Add initial OSS part of license expiry code. (#16753)

In “tsh login” show only alerts with on-login label.
    In “tsh status” show only alerts with “high” severity. Which license warning should match.
    In all “tctl” commands show only alerts with “high” severity.
This commit is contained in:
Edward Dowling 2022-10-13 15:59:47 +01:00 committed by GitHub
parent b289295f93
commit d0a602deb6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 184 additions and 18 deletions

View file

@ -19,6 +19,7 @@ package constants
import (
"encoding/json"
"time"
"github.com/gravitational/trace"
)
@ -347,3 +348,8 @@ const (
// SupportedAzureDiscoveryServices is list of Azure services currently
// supported by the Teleport discovery service.
var SupportedAzureDiscoveryServices = []string{AzureServiceTypeKubernetes}
const (
// TimeoutGetClusterAlerts is the timeout for grabbing cluster alerts from tctl and tsh
TimeoutGetClusterAlerts = time.Millisecond * 500
)

View file

@ -485,6 +485,9 @@ const (
// a mechanism for reducing noise/redundancy, and is not a form of access control. Use
// one of the "permit" labels if you need to restrict viewership of an alert.
AlertSupersedes = "teleport.internal/alert-supersedes"
// AlertLicenseExpired is an internal label that indicates that the license has expired.
AlertLicenseExpired = "teleport.internal/license-expired-warning"
)
// RequestableResourceKinds lists all Teleport resource kinds users can request access to.

2
e

@ -1 +1 @@
Subproject commit 93a19fd5eda61fab3464f77aeabc50eea7594e8e
Subproject commit aa71cbb7e14c9f1bda4115515815c3008294e65c

View file

@ -39,6 +39,7 @@ import (
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/types"
)
type LoggingPurpose int
@ -522,3 +523,26 @@ type PredicateError struct {
func (p PredicateError) Error() string {
return fmt.Sprintf("%s\nCheck syntax at https://goteleport.com/docs/setup/reference/predicate-language/#resource-filtering", p.Err.Error())
}
// FormatAlert formats and colors the alert message if possible.
func FormatAlert(alert types.ClusterAlert) string {
// TODO(timothyb89): Due to complications with globally enabling +
// properly resetting Windows terminal ANSI processing, for now we just
// disable color output. Otherwise, raw ANSI escapes will be visible to
// users.
var buf bytes.Buffer
switch runtime.GOOS {
case constants.WindowsOS:
fmt.Fprint(&buf, alert.Spec.Message)
default:
switch alert.Spec.Severity {
case types.AlertSeverity_HIGH:
fmt.Fprint(&buf, Color(Red, alert.Spec.Message))
case types.AlertSeverity_MEDIUM:
fmt.Fprint(&buf, Color(Yellow, alert.Spec.Message))
default:
fmt.Fprint(&buf, alert.Spec.Message)
}
}
return buf.String()
}

View file

@ -17,6 +17,7 @@ limitations under the License.
package common
import (
"context"
"encoding/json"
"fmt"
"io"
@ -27,6 +28,7 @@ import (
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/lib/asciitable"
"github.com/gravitational/teleport/lib/utils"
@ -84,7 +86,7 @@ func (e *SessionsCollection) WriteYAML(w io.Writer) error {
return utils.WriteYAML(w, e.SessionEvents)
}
// ShowSessions is s helper function for displaying listed sessions via tsh or tctl
// ShowSessions is a helper function for displaying listed sessions via tsh or tctl.
func ShowSessions(events []events.AuditEvent, format string, w io.Writer) error {
sessions := &SessionsCollection{SessionEvents: events}
switch format {
@ -98,3 +100,35 @@ func ShowSessions(events []events.AuditEvent, format string, w io.Writer) error
return trace.BadParameter("unknown format %q", format)
}
}
// ClusterAlertGetter manages getting cluster alerts.
type ClusterAlertGetter interface {
GetClusterAlerts(ctx context.Context, query types.GetClusterAlertsRequest) ([]types.ClusterAlert, error)
}
// ShowClusterAlerts shows cluster alerts with the given labels and severity.
func ShowClusterAlerts(ctx context.Context, client ClusterAlertGetter, w io.Writer, labels map[string]string, minSeverity, maxSeverity types.AlertSeverity) error {
// get any "on login" alerts
alertCtx, cancelAlertCtx := context.WithTimeout(ctx, constants.TimeoutGetClusterAlerts)
defer cancelAlertCtx()
alerts, err := client.GetClusterAlerts(alertCtx, types.GetClusterAlertsRequest{
Labels: labels,
Severity: minSeverity,
})
if err != nil && !trace.IsNotImplemented(err) {
return trace.Wrap(err)
}
types.SortClusterAlerts(alerts)
var errs []error
for _, alert := range alerts {
if err := alert.CheckMessage(); err != nil {
errs = append(errs, trace.Errorf("invalid alert %q: %w", alert.Metadata.Name, err))
continue
}
if alert.Spec.Severity <= maxSeverity {
fmt.Fprintf(w, "%s\n\n", utils.FormatAlert(alert))
}
}
return trace.NewAggregate(errs...)
}

View file

@ -0,0 +1,91 @@
// Copyright 2022 Gravitational, Inc
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package common
import (
"bytes"
"context"
"testing"
"github.com/gravitational/teleport/api/types"
"github.com/stretchr/testify/require"
)
type mockedAlertGetter struct {
alerts []types.ClusterAlert
}
func (mag *mockedAlertGetter) GetClusterAlerts(ctx context.Context, query types.GetClusterAlertsRequest) ([]types.ClusterAlert, error) {
return mag.alerts, nil
}
func mockAlertGetter(alerts []types.ClusterAlert) ClusterAlertGetter {
return &mockedAlertGetter{
alerts: alerts,
}
}
func TestShowClusterAlerts(t *testing.T) {
tests := map[string]struct {
alerts []types.ClusterAlert
wantOut string
}{
"No filtered severities": {
alerts: []types.ClusterAlert{
{
Spec: types.ClusterAlertSpec{
Severity: types.AlertSeverity_MEDIUM,
Message: "someMessage",
},
},
},
wantOut: "\x1b[33msomeMessage\x1b[0m\n\n",
},
"Filtered severities": {
alerts: []types.ClusterAlert{
{
ResourceHeader: types.ResourceHeader{
Metadata: types.Metadata{
Labels: map[string]string{
"someLabel": "yes",
},
},
},
Spec: types.ClusterAlertSpec{
Severity: types.AlertSeverity_HIGH,
Message: "someOtherMessage",
},
}, {
Spec: types.ClusterAlertSpec{
Severity: types.AlertSeverity_MEDIUM,
Message: "someMessage",
},
},
},
wantOut: "\x1b[33msomeMessage\x1b[0m\n\n",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
alertGetter := mockAlertGetter(test.alerts)
var got bytes.Buffer
err := ShowClusterAlerts(context.Background(), alertGetter, &got, nil, types.AlertSeverity_LOW, types.AlertSeverity_MEDIUM)
require.NoError(t, err)
require.Equal(t, test.wantOut, got.String())
})
}
}

View file

@ -43,6 +43,7 @@ import (
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/service"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/tool/common"
toolcommon "github.com/gravitational/teleport/tool/common"
)
@ -206,6 +207,12 @@ func TryRun(commands []CLICommand, args []string) error {
break
}
}
if err := common.ShowClusterAlerts(ctx, client, os.Stderr, nil,
types.AlertSeverity_HIGH, types.AlertSeverity_HIGH); err != nil {
log.WithError(err).Warn("Failed to display cluster alerts.")
}
return nil
}

View file

@ -1584,24 +1584,14 @@ func onLogin(cf *CLIConf) error {
fmt.Fprintf(os.Stderr, "%s\n\n", warning)
}
// get any "on login" alerts
alerts, err := tc.GetClusterAlerts(cf.Context, types.GetClusterAlertsRequest{
Labels: map[string]string{
types.AlertOnLogin: "yes",
},
})
if err != nil && !trace.IsNotImplemented(err) {
return trace.Wrap(err)
// Show on-login alerts, all high severity alerts are shown by onStatus
// so can be excluded here.
if err := common.ShowClusterAlerts(cf.Context, tc, os.Stderr, map[string]string{
types.AlertOnLogin: "yes",
}, types.AlertSeverity_LOW, types.AlertSeverity_MEDIUM); err != nil {
log.WithError(err).Warn("Failed to display cluster alerts.")
}
types.SortClusterAlerts(alerts)
for _, alert := range alerts {
if err := alert.CheckMessage(); err != nil {
log.Warnf("Skipping invalid alert %q: %v", alert.Metadata.Name, err)
}
fmt.Fprintf(os.Stderr, "%s\n\n", alert.Spec.Message)
}
// NOTE: we currently print all alerts that are marked as `on-login`, because we
// don't use the alert API very heavily. If we start to make more use of it, we
// could probably add a separate `tsh alerts ls` command, and truncate the list
@ -3425,6 +3415,17 @@ func onStatus(cf *CLIConf) error {
return trace.NotFound("Active profile expired.")
}
tc, err := makeClient(cf, true)
if err != nil {
log.WithError(err).Warn("Failed to make client for retrieving cluster alerts.")
return nil
}
if err := common.ShowClusterAlerts(cf.Context, tc, os.Stderr, nil,
types.AlertSeverity_HIGH, types.AlertSeverity_HIGH); err != nil {
log.WithError(err).Warn("Failed to display cluster alerts.")
}
return nil
}