Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions api/gen/proto/go/teleport/decision/v1alpha1/ssh_identity.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions api/proto/teleport/decision/v1alpha1/ssh_identity.proto
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ message SSHIdentity {
// ScopePin is an optional pin that ties the certificate to a specific scope and set of scoped roles. When
// set, the Roles field must not be set.
teleport.scopes.v1.Pin scope_pin = 35;

// The scope associated with a host identity.
string agent_scope = 36;
}

// CertExtensionMode specifies the type of extension to use in the cert. This type
Expand Down
11 changes: 11 additions & 0 deletions api/types/provisioning.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ type ProvisionToken interface {
// join methods where the name is secret. This should be used when logging
// the token name.
GetSafeName() string

// GetAssignedScope always returns an empty string because a [ProvisionToken] is always
// unscoped
GetAssignedScope() string

// Clone creates a copy of the token.
Clone() ProvisionToken
}
Expand Down Expand Up @@ -652,6 +657,12 @@ func (p *ProvisionTokenV2) GetSafeName() string {
return name
}

// GetAssignedScope always returns an empty string because a [ProvisionTokenV2] is always
// unscoped
func (p *ProvisionTokenV2) GetAssignedScope() string {
return ""
}

// String returns the human readable representation of a provisioning token.
func (p ProvisionTokenV2) String() string {
expires := "never"
Expand Down
9 changes: 7 additions & 2 deletions constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,9 +464,14 @@ const (
)

const (
// CertExtensionScopePin is used to pin a certificate to a specific scope and
// set of scoped roles.
// CertExtensionScopePin is used to pin a user certificate to a specific scope and
// set of scoped roles. This constrains a user's access to resources based on both
// the scoping rules and scoped roles defined.
CertExtensionScopePin = "scope-pin@goteleport.com"
// CertExtensionAgentScope is used to pin an agent/host certificate to a specific scope.
// This constrains other identities' access to the agent itself as well as the agent's
// access to other resources based on scoping rules.
CertExtensionAgentScope = "agent-scope@goteleport.com"
// CertExtensionPermitX11Forwarding allows X11 forwarding for certificate
CertExtensionPermitX11Forwarding = "permit-X11-forwarding"
// CertExtensionPermitAgentForwarding allows agent forwarding for certificate
Expand Down
2 changes: 1 addition & 1 deletion integration/utmp_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ func newSrvCtx(ctx context.Context, t *testing.T) *SrvCtx {
Role: types.RoleNode,
PublicSSHKey: sshPublicKey,
PublicTLSKey: tlsPublicKey,
})
}, "")
require.NoError(t, err)

// set up user CA and set up a user that has access to the server
Expand Down
5 changes: 4 additions & 1 deletion lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -5128,7 +5128,7 @@ func ExtractHostID(hostName string, clusterName string) (string, error) {

// GenerateHostCerts generates new host certificates (signed
// by the host certificate authority) for a node.
func (a *Server) GenerateHostCerts(ctx context.Context, req *proto.HostCertsRequest) (*proto.Certs, error) {
func (a *Server) GenerateHostCerts(ctx context.Context, req *proto.HostCertsRequest, scope string) (*proto.Certs, error) {
if err := req.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
Expand Down Expand Up @@ -5262,6 +5262,7 @@ func (a *Server) GenerateHostCerts(ctx context.Context, req *proto.HostCertsRequ
ClusterName: clusterName.GetClusterName(),
SystemRole: req.Role,
Principals: req.AdditionalPrincipals,
AgentScope: scope,
},
})
if err != nil {
Expand All @@ -5283,6 +5284,7 @@ func (a *Server) GenerateHostCerts(ctx context.Context, req *proto.HostCertsRequ
Groups: []string{req.Role.String()},
TeleportCluster: clusterName.GetClusterName(),
SystemRoles: systemRoles,
AgentScope: scope,
}
subject, err := identity.Subject()
if err != nil {
Expand Down Expand Up @@ -5315,6 +5317,7 @@ func (a *Server) GenerateHostCerts(ctx context.Context, req *proto.HostCertsRequ
if err != nil {
return nil, trace.Wrap(err)
}

return &proto.Certs{
SSH: hostSSHCert,
TLS: hostTLSCert,
Expand Down
3 changes: 2 additions & 1 deletion lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,8 @@ func (a *ServerWithRoles) GenerateHostCerts(ctx context.Context, req *proto.Host
if !a.hasBuiltinRole(req.Role) && req.Role != types.RoleInstance {
return nil, trace.AccessDenied("roles do not match: %v and %v", existingRoles, req.Role)
}
return a.authServer.GenerateHostCerts(ctx, req)
identity := a.context.Identity.GetIdentity()
return a.authServer.GenerateHostCerts(ctx, req, identity.AgentScope)
Comment thread
eriktate marked this conversation as resolved.
}

// checkAdditionalSystemRoles verifies additional system roles in host cert request.
Expand Down
60 changes: 60 additions & 0 deletions lib/auth/auth_with_roles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7246,6 +7246,66 @@ func TestGenerateHostCert(t *testing.T) {
}
}

// newScopedTestServerForHost creates a self-cleaning `ServerWithRoles`, configured
// for a given host
func newScopedTestServerForHost(t *testing.T, srv *authtest.AuthServer, hostID, scope string, role types.SystemRole) *auth.ServerWithRoles {
authzContext := authz.ContextWithUser(t.Context(), authtest.TestScopedHost(srv.ClusterName, hostID, scope, role).I)
ctxIdentity, err := srv.Authorizer.Authorize(authzContext)
require.NoError(t, err)

authWithRole := auth.NewServerWithRoles(
srv.AuthServer,
srv.AuditLog,
*ctxIdentity,
)

t.Cleanup(func() { authWithRole.Close() })

return authWithRole
}

func TestGenerateHostCertsScoped(t *testing.T) {
ctx := context.Background()
srv := newTestTLSServer(t)

scope := "/aa/bb"
hostID := "testhost"
roles := types.SystemRoles{types.RoleNode}

s := newScopedTestServerForHost(t, srv.AuthServer, hostID, scope, types.RoleNode)

_, sshPub, err := testauthority.New().GenerateKeyPair()
require.NoError(t, err)
tlsKey, err := cryptosuites.GenerateKeyWithAlgorithm(cryptosuites.ECDSAP256)
require.NoError(t, err)
tlsPubPEM, err := keys.MarshalPublicKey(tlsKey.Public())
require.NoError(t, err)

certs, err := s.GenerateHostCerts(ctx, &proto.HostCertsRequest{
PublicTLSKey: tlsPubPEM,
PublicSSHKey: sshPub,
HostID: hostID,
Role: types.RoleInstance,
SystemRoles: roles,
})
require.NoError(t, err)

// ensure scope encoded in TLS cert matches the auth identity
tlsCert, err := tlsca.ParseCertificatePEM(certs.TLS)
require.NoError(t, err)

tlsIdent, err := tlsca.FromSubject(tlsCert.Subject, tlsCert.NotAfter)
require.NoError(t, err)
require.Equal(t, scope, tlsIdent.AgentScope)

// ensure scope encoded in SSH cert matches the auth identity
sshCert, err := sshutils.ParseCertificate(certs.SSH)
require.NoError(t, err)
sshIdent, err := sshca.DecodeIdentity(sshCert)
require.NoError(t, err)
require.Equal(t, scope, sshIdent.AgentScope)
}

// TestLocalServiceRolesHavePermissionsForUploaderService verifies that all of Teleport's
// builtin roles have permissions to execute the calls required by the uploader service.
// This is because only one uploader service runs per Teleport process, and it will use
Expand Down
24 changes: 21 additions & 3 deletions lib/auth/authtest/authtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,7 @@ func generateCertificate(authServer *auth.Server, identity TestIdentity) ([]byte
PublicTLSKey: tlsPublicKeyPEM,
PublicSSHKey: sshPublicKeyPEM,
SystemRoles: id.AdditionalSystemRoles,
})
}, "")
if err != nil {
return nil, nil, trace.Wrap(err)
}
Expand All @@ -709,7 +709,7 @@ func generateCertificate(authServer *auth.Server, identity TestIdentity) ([]byte
Role: id.Role,
PublicTLSKey: tlsPublicKeyPEM,
PublicSSHKey: sshPublicKeyPEM,
})
}, "")
if err != nil {
return nil, nil, trace.Wrap(err)
}
Expand Down Expand Up @@ -1044,6 +1044,24 @@ func TestBuiltin(role types.SystemRole) TestIdentity {
}
}

// TestScopedHost returns TestIdentity for a scoped host
func TestScopedHost(clusterName, hostID, scope string, role types.SystemRole) TestIdentity {
username := hostID
if clusterName != "" {
username = utils.HostFQDN(hostID, clusterName)
}
return TestIdentity{
I: authz.BuiltinRole{
Role: types.RoleInstance,
Username: username,
AdditionalSystemRoles: types.SystemRoles{role},
Identity: tlsca.Identity{
AgentScope: scope,
},
},
}
}

// TestServerID returns a TestIdentity for a node with the passed in serverID.
func TestServerID(role types.SystemRole, serverID string) TestIdentity {
return TestIdentity{
Expand Down Expand Up @@ -1321,7 +1339,7 @@ func NewServerIdentity(clt *auth.Server, hostID string, role types.SystemRole) (
Role: role,
PublicSSHKey: ssh.MarshalAuthorizedKey(sshPubKey),
PublicTLSKey: tlsPubKey,
})
}, "")
if err != nil {
return nil, trace.Wrap(err)
}
Expand Down
7 changes: 4 additions & 3 deletions lib/auth/grpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -6070,9 +6070,10 @@ func NewGRPCServer(cfg GRPCServerConfig) (*GRPCServer, error) {
authpb.RegisterJoinServiceServer(server, legacyJoinServiceServer)

joinv1.RegisterJoinServiceServer(server, join.NewServer(&join.ServerConfig{
Authorizer: cfg.Authorizer,
AuthService: cfg.AuthServer,
FIPS: cfg.AuthServer.fips,
Authorizer: cfg.Authorizer,
AuthService: cfg.AuthServer,
FIPS: cfg.AuthServer.fips,
ScopedTokenService: cfg.AuthServer.Services,
}))

integrationServiceServer, err := integrationv1.NewService(&integrationv1.ServiceConfig{
Expand Down
2 changes: 1 addition & 1 deletion lib/auth/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -1585,7 +1585,7 @@ func GenerateIdentity(a *Server, id state.IdentityID, additionalPrincipals, dnsN
DNSNames: dnsNames,
PublicSSHKey: ssh.MarshalAuthorizedKey(sshPub),
PublicTLSKey: tlsPub,
})
}, "")
if err != nil {
return nil, trace.Wrap(err)
}
Expand Down
Loading
Loading