Skip to content

Commit

Permalink
Add the cert.create event (#9822) (#10226)
Browse files Browse the repository at this point in the history
* Add the `cert.create` event

For now, this is only emitted for user certificate issuance.

* Make `cert_type` a string rather than an enum

* Match field names and json tags in events.Identity

* Change events.Identity.Traits to be a wrappers.LabelValues/wrappers.Traits

* Event code shouldn't be under T10xx anymore
  • Loading branch information
espadolini authored Feb 14, 2022
1 parent 9bf1cdc commit 496e07e
Show file tree
Hide file tree
Showing 8 changed files with 5,806 additions and 3,504 deletions.
9,122 changes: 5,618 additions & 3,504 deletions api/types/events/events.pb.go

Large diffs are not rendered by default.

109 changes: 109 additions & 0 deletions api/types/events/events.proto
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import "gogoproto/gogo.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/struct.proto";

import "github.com/gravitational/teleport/api/types/wrappers/wrappers.proto";

option (gogoproto.marshaler_all) = true;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.goproto_getters_all) = false;
Expand Down Expand Up @@ -1286,6 +1288,19 @@ message BillingCardDelete {
[ (gogoproto.nullable) = false, (gogoproto.embed) = true, (gogoproto.jsontag) = "" ];
}

// CertificateCreate is emitted when a certificate is issued.
message CertificateCreate {
// Metadata is a common event metadata.
Metadata Metadata = 1
[ (gogoproto.nullable) = false, (gogoproto.embed) = true, (gogoproto.jsontag) = "" ];

// CertificateType is the type of certificate that was just issued.
string CertificateType = 2 [ (gogoproto.jsontag) = "cert_type,omitempty" ];

// Identity is the identity associated with the certificate, as interpreted by Teleport.
Identity Identity = 3 [ (gogoproto.jsontag) = "identity" ];
}

// OneOf is a union of one of audit events submitted to the auth service
message OneOf {
// Event is one of the audit events
Expand Down Expand Up @@ -1339,6 +1354,7 @@ message OneOf {
events.BillingCardCreate BillingCardCreate = 47;
events.BillingCardDelete BillingCardDelete = 48;
events.AccessRequestDelete AccessRequestDelete = 66;
events.CertificateCreate CertificateCreate = 68;
}
}

Expand Down Expand Up @@ -1369,3 +1385,96 @@ message SessionUpload {
// URL is where the url the session event data upload is at
string SessionURL = 5 [ (gogoproto.jsontag) = "url" ];
}

// Identity matches github.com/gravitational/teleport/lib/tlsca.Identity except
// for RouteToApp and RouteToDatabase which are nullable and Traits which is
// represented as a google.protobuf.Struct (still containing a map from string
// to strings). Field names match other names already used in other events
// rather than the field names in tlsca.Identity.
message Identity {
// User is a username or name of the node connection
string User = 1 [ (gogoproto.jsontag) = "user,omitempty" ];
// Impersonator is a username of a user impersonating this user
string Impersonator = 2 [ (gogoproto.jsontag) = "impersonator,omitempty" ];
// Roles is a list of groups (Teleport roles) encoded in the identity
repeated string Roles = 3 [ (gogoproto.jsontag) = "roles,omitempty" ];
// Usage is a list of usage restrictions encoded in the identity
repeated string Usage = 4 [ (gogoproto.jsontag) = "usage,omitempty" ];
// Logins is a list of Unix logins allowed.
repeated string Logins = 5 [ (gogoproto.jsontag) = "logins,omitempty" ];
// KubernetesGroups is a list of Kubernetes groups allowed
repeated string KubernetesGroups = 6 [ (gogoproto.jsontag) = "kubernetes_groups,omitempty" ];
// KubernetesUsers is a list of Kubernetes users allowed
repeated string KubernetesUsers = 7 [ (gogoproto.jsontag) = "kubernetes_users,omitempty" ];
// Expires specifies whenever the session will expire
google.protobuf.Timestamp Expires = 8 [
(gogoproto.stdtime) = true,
(gogoproto.nullable) = false,
(gogoproto.jsontag) = "expires"
];
// RouteToCluster specifies the target cluster
// if present in the session
string RouteToCluster = 9 [ (gogoproto.jsontag) = "route_to_cluster,omitempty" ];
// KubernetesCluster specifies the target kubernetes cluster for TLS
// identities. This can be empty on older Teleport clients.
string KubernetesCluster = 10 [ (gogoproto.jsontag) = "kubernetes_cluster,omitempty" ];
// Traits hold claim data used to populate a role at runtime.
wrappers.LabelValues Traits = 11 [
(gogoproto.nullable) = false,
(gogoproto.jsontag) = "traits,omitempty",
(gogoproto.customtype) = "github.com/gravitational/teleport/api/types/wrappers.Traits"
];
// RouteToApp holds routing information for applications. Routing metadata
// allows Teleport web proxy to route HTTP requests to the appropriate
// cluster and Teleport application proxy within the cluster.
RouteToApp RouteToApp = 12 [ (gogoproto.jsontag) = "route_to_app,omitempty" ];
// TeleportCluster is the name of the teleport cluster that this identity
// originated from. For TLS certs this may not be the same as cert issuer,
// in case of multi-hop requests that originate from a remote cluster.
string TeleportCluster = 13 [ (gogoproto.jsontag) = "teleport_cluster,omitempty" ];
// RouteToDatabase contains routing information for databases.
RouteToDatabase RouteToDatabase = 14 [ (gogoproto.jsontag) = "route_to_database,omitempty" ];
// DatabaseNames is a list of allowed database names.
repeated string DatabaseNames = 15 [ (gogoproto.jsontag) = "database_names,omitempty" ];
// DatabaseUsers is a list of allowed database users.
repeated string DatabaseUsers = 16 [ (gogoproto.jsontag) = "database_users,omitempty" ];
// MFADeviceUUID is the UUID of an MFA device when this Identity was
// confirmed immediately after an MFA check.
string MFADeviceUUID = 17 [ (gogoproto.jsontag) = "mfa_device_uuid,omitempty" ];
// ClientIP is an observed IP of the client that this Identity represents.
string ClientIP = 18 [ (gogoproto.jsontag) = "client_ip,omitempty" ];
// AWSRoleARNs is a list of allowed AWS role ARNs user can assume.
repeated string AWSRoleARNs = 19 [ (gogoproto.jsontag) = "aws_role_arns,omitempty" ];
// AccessRequests is a list of UUIDs of active requests for this Identity.
repeated string AccessRequests = 20 [ (gogoproto.jsontag) = "access_requests,omitempty" ];
// DisallowReissue is a flag that, if set, instructs the auth server to
// deny any attempts to reissue new certificates while authenticated with
// this certificate.
bool DisallowReissue = 21 [ (gogoproto.jsontag) = "disallow_reissue,omitempty" ];
}

// RouteToApp contains parameters for application access certificate requests.
message RouteToApp {
// Name is the application name certificate is being requested for.
string Name = 1 [ (gogoproto.jsontag) = "name" ];
// SessionID is the ID of the application session.
string SessionID = 2 [ (gogoproto.jsontag) = "session_id" ];
// PublicAddr is the application public address.
string PublicAddr = 3 [ (gogoproto.jsontag) = "public_addr" ];
// ClusterName is the cluster where the application resides.
string ClusterName = 4 [ (gogoproto.jsontag) = "cluster_name" ];
// AWSRoleARN is the AWS role to assume when accessing AWS API.
string AWSRoleARN = 5 [ (gogoproto.jsontag) = "aws_role_arn,omitempty" ];
}

// RouteToDatabase combines parameters for database service routing information.
message RouteToDatabase {
// ServiceName is the Teleport database proxy service name the cert is for.
string ServiceName = 1 [ (gogoproto.jsontag) = "service_name" ];
// Protocol is the type of the database the cert is for.
string Protocol = 2 [ (gogoproto.jsontag) = "protocol" ];
// Username is an optional database username to embed.
string Username = 3 [ (gogoproto.jsontag) = "username,omitempty" ];
// Database is an optional database name to embed.
string Database = 4 [ (gogoproto.jsontag) = "database,omitempty" ];
}
4 changes: 4 additions & 0 deletions api/types/events/oneof.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ func ToOneOf(in AuditEvent) (*OneOf, error) {
out.Event = &OneOf_AccessRequestDelete{
AccessRequestDelete: e,
}
case *CertificateCreate:
out.Event = &OneOf_CertificateCreate{
CertificateCreate: e,
}
default:
return nil, trace.BadParameter("event type %T is not supported", in)
}
Expand Down
15 changes: 15 additions & 0 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/types"
apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/api/types/wrappers"
apiutils "github.com/gravitational/teleport/api/utils"
"github.com/gravitational/teleport/lib/auth/u2f"
Expand Down Expand Up @@ -837,6 +838,20 @@ func (a *Server) generateUserCert(req certRequest) (*certs, error) {
if err != nil {
return nil, trace.Wrap(err)
}

eventIdentity := identity.GetEventIdentity()
eventIdentity.Expires = certRequest.NotAfter
if a.emitter.EmitAuditEvent(a.closeCtx, &apievents.CertificateCreate{
Metadata: apievents.Metadata{
Type: events.CertificateCreateEvent,
Code: events.CertificateCreateCode,
},
CertificateType: events.CertificateTypeUser,
Identity: &eventIdentity,
}); err != nil {
log.WithError(err).Warn("Failed to emit certificate create event.")
}

return &certs{ssh: sshCert, tls: tlsCert}, nil
}

Expand Down
6 changes: 6 additions & 0 deletions lib/events/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,12 @@ const (
MFADeviceAddEvent = "mfa.add"
// MFADeviceDeleteEvent is an event type for users deleting MFA devices.
MFADeviceDeleteEvent = "mfa.delete"

// CertificateCreateEvent is emitted when a certificate is issued.
CertificateCreateEvent = "cert.create"

// CertificateTypeUser is the CertificateType for certificate events pertaining to user certificates.
CertificateTypeUser = "user"
)

const (
Expand Down
3 changes: 3 additions & 0 deletions lib/events/codes.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,4 +419,7 @@ const (
RoleCreatedCode = "T9000I"
// RoleDeletedCode is the role deleted event code.
RoleDeletedCode = "T9001I"

// CertificateCreateCode is the certificate issuance event code.
CertificateCreateCode = "TC000I"
)
6 changes: 6 additions & 0 deletions lib/events/dynamic.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,12 @@ func FromEventFields(fields EventFields) (AuditEvent, error) {
return nil, trace.Wrap(err)
}
return &e, nil
case CertificateCreateEvent:
var e events.CertificateCreate
if err := utils.FastUnmarshal(data, &e); err != nil {
return nil, trace.Wrap(err)
}
return &e, nil
default:
return nil, trace.BadParameter("unknown event type: %q", eventType)
}
Expand Down
45 changes: 45 additions & 0 deletions lib/tlsca/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,51 @@ func (id *Identity) GetRouteToApp() (RouteToApp, error) {
return id.RouteToApp, nil
}

func (id *Identity) GetEventIdentity() events.Identity {
// leave a nil instead of a zero struct so the field doesn't appear when
// serialized as json
var routeToApp *events.RouteToApp
if id.RouteToApp != (RouteToApp{}) {
routeToApp = &events.RouteToApp{
Name: id.RouteToApp.Name,
SessionID: id.RouteToApp.SessionID,
PublicAddr: id.RouteToApp.PublicAddr,
ClusterName: id.RouteToApp.ClusterName,
}
}
var routeToDatabase *events.RouteToDatabase
if id.RouteToDatabase != (RouteToDatabase{}) {
routeToDatabase = &events.RouteToDatabase{
ServiceName: id.RouteToDatabase.ServiceName,
Protocol: id.RouteToDatabase.Protocol,
Username: id.RouteToDatabase.Username,
Database: id.RouteToDatabase.Database,
}
}

return events.Identity{
User: id.Username,
Impersonator: id.Impersonator,
Roles: id.Groups,
Usage: id.Usage,
Logins: id.Principals,
KubernetesGroups: id.KubernetesGroups,
KubernetesUsers: id.KubernetesUsers,
Expires: id.Expires,
RouteToCluster: id.RouteToCluster,
KubernetesCluster: id.KubernetesCluster,
Traits: id.Traits,
RouteToApp: routeToApp,
TeleportCluster: id.TeleportCluster,
RouteToDatabase: routeToDatabase,
DatabaseNames: id.DatabaseNames,
DatabaseUsers: id.DatabaseUsers,
MFADeviceUUID: id.MFAVerified,
ClientIP: id.ClientIP,
AccessRequests: id.ActiveRequests,
}
}

// CheckAndSetDefaults checks and sets default values
func (id *Identity) CheckAndSetDefaults() error {
if id.Username == "" {
Expand Down

0 comments on commit 496e07e

Please sign in to comment.