Skip to content

Commit

Permalink
Adding OIDC auth functionality to the Azure integration (#51219)
Browse files Browse the repository at this point in the history
* Protobuf and configuration for Access Graph Azure Discovery

* Fixing rebase after protobuf gen

* Updating to use existing msgraph client

* PR feedback

* Using variadic options

* Removing memberOf expansion

* Expanding memberships by calling memberOf on each user

* PR feedback

* Rebase go.sum stuff

* Go mod tidy

* Fixing go.mod

* Update lib/msgraph/paginated.go

Co-authored-by: Tiago Silva <tiago.silva@goteleport.com>

* PR feedback

* Protobuf and configuration for Access Graph Azure Discovery

* Adding Azure sync functionality which can be called by the Azure fetcher

* Protobuf update

* Linting

* PR feedback

* PR feedback

* Updating to use existing msgraph client

* PR feedback

* Using variadic options

* Removing memberOf expansion

* Expanding memberships by calling memberOf on each user

* PR feedback

* Rebase go.sum stuff

* PR feedback

* Protobuf and configuration for Access Graph Azure Discovery

* Protobuf gen fix

* Rebase fixes

* More cleanup

* e ref update

* Invoking token generation and returning the response

* Quick test with a message to make sure RPC is invoked

* Skeleton of new Azure OIDC RPC call

* Fetching the Azure OIDC token during fetcher creation and establishing a credential assertion approach

* PR feedback; restricting token requests to auth, discovery, and proxy roles.

* Lint

* Fixing mocks

* Fix imports

* Fix test

* Rebase fxes

* Adding back OIDC fetching, accidentally removed it during rebase

* e ref

* Lint

* Fix imports

---------

Co-authored-by: Tiago Silva <tiago.silva@goteleport.com>
  • Loading branch information
mvbrock and tigrato authored Jan 23, 2025
1 parent 0550380 commit bc3a767
Show file tree
Hide file tree
Showing 13 changed files with 542 additions and 144 deletions.
12 changes: 12 additions & 0 deletions api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4835,6 +4835,18 @@ func (c *Client) GenerateAWSOIDCToken(ctx context.Context, integration string) (
return resp.GetToken(), nil
}

// GenerateAzureOIDCToken generates a token to be used when executing an Azure OIDC Integration action.
func (c *Client) GenerateAzureOIDCToken(ctx context.Context, integration string) (string, error) {
resp, err := c.integrationsClient().GenerateAzureOIDCToken(ctx, &integrationpb.GenerateAzureOIDCTokenRequest{
Integration: integration,
})
if err != nil {
return "", trace.Wrap(err)
}

return resp.GetToken(), nil
}

// PluginsClient returns an unadorned Plugins client, using the underlying
// Auth gRPC connection.
// Clients connecting to non-Enterprise clusters, or older Teleport versions,
Expand Down
395 changes: 255 additions & 140 deletions api/gen/proto/go/teleport/integration/v1/integration_service.pb.go

Large diffs are not rendered by default.

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

17 changes: 17 additions & 0 deletions api/proto/teleport/integration/v1/integration_service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ service IntegrationService {
// GenerateAWSOIDCToken generates a token to be used when executing an AWS OIDC Integration action.
rpc GenerateAWSOIDCToken(GenerateAWSOIDCTokenRequest) returns (GenerateAWSOIDCTokenResponse);

// GenerateAzureOIDCToken generates a token to be used when executing an Azure OIDC Integration action.
rpc GenerateAzureOIDCToken(GenerateAzureOIDCTokenRequest) returns (GenerateAzureOIDCTokenResponse);

// GenerateGitHubUserCert signs a SSH certificate for GitHub integration.
rpc GenerateGitHubUserCert(GenerateGitHubUserCertRequest) returns (GenerateGitHubUserCertResponse);

Expand Down Expand Up @@ -119,6 +122,20 @@ message GenerateAWSOIDCTokenResponse {
string token = 1;
}

// GenerateAzureOIDCTokenRequest are the parameters used to request an Azure OIDC
// Integration token.
message GenerateAzureOIDCTokenRequest {
// Integration is the Azure OIDC Integration name.
// Required.
string integration = 1;
}

// GenerateAzureOIDCTokenResponse contains a signed Azure OIDC Integration token.
message GenerateAzureOIDCTokenResponse {
// Token is the signed JWT ready to be used
string token = 1;
}

// GenerateGitHubUserCertRequest is a request to sign a client certificate used by
// GitHub integration to authenticate with GitHub enterprise.
message GenerateGitHubUserCertRequest {
Expand Down
5 changes: 5 additions & 0 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,11 @@ func (r *Services) GenerateAWSOIDCToken(ctx context.Context, integration string)
return r.IntegrationsTokenGenerator.GenerateAWSOIDCToken(ctx, integration)
}

// GenerateAzureOIDCToken generates a token to be used to execute an Azure OIDC Integration action.
func (r *Services) GenerateAzureOIDCToken(ctx context.Context, integration string) (string, error) {
return r.IntegrationsTokenGenerator.GenerateAzureOIDCToken(ctx, integration)
}

var (
generateRequestsCount = prometheus.NewCounter(
prometheus.CounterOpts{
Expand Down
8 changes: 8 additions & 0 deletions lib/auth/authclient/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -760,6 +760,9 @@ type DiscoveryAccessPoint interface {
// GenerateAWSOIDCToken generates a token to be used to execute an AWS OIDC Integration action.
GenerateAWSOIDCToken(ctx context.Context, integration string) (string, error)

// GenerateAzureOIDCToken generates a token to be used to execute an Azure OIDC Integration action.
GenerateAzureOIDCToken(ctx context.Context, integration string) (string, error)

// EnrollEKSClusters enrolls EKS clusters into Teleport by installing teleport-kube-agent chart on the clusters.
EnrollEKSClusters(context.Context, *integrationpb.EnrollEKSClustersRequest, ...grpc.CallOption) (*integrationpb.EnrollEKSClustersResponse, error)

Expand Down Expand Up @@ -1437,6 +1440,11 @@ func (w *DiscoveryWrapper) GenerateAWSOIDCToken(ctx context.Context, integration
return w.NoCache.GenerateAWSOIDCToken(ctx, integration)
}

// GenerateAzureOIDCToken generates a token to be used to execute an Azure OIDC Integration action.
func (w *DiscoveryWrapper) GenerateAzureOIDCToken(ctx context.Context, integration string) (string, error) {
return w.NoCache.GenerateAzureOIDCToken(ctx, integration)
}

// EnrollEKSClusters enrolls EKS clusters into Teleport by installing teleport-kube-agent chart on the clusters.
func (w *DiscoveryWrapper) EnrollEKSClusters(ctx context.Context, req *integrationpb.EnrollEKSClustersRequest, _ ...grpc.CallOption) (*integrationpb.EnrollEKSClustersResponse, error) {
return w.NoCache.EnrollEKSClusters(ctx, req)
Expand Down
3 changes: 3 additions & 0 deletions lib/auth/authclient/clt.go
Original file line number Diff line number Diff line change
Expand Up @@ -1703,6 +1703,9 @@ type ClientI interface {
// GenerateAWSOIDCToken generates a token to be used to execute an AWS OIDC Integration action.
GenerateAWSOIDCToken(ctx context.Context, integration string) (string, error)

// GenerateAzureOIDCToken generates a token to be used to execute an Azure OIDC Integration action.
GenerateAzureOIDCToken(ctx context.Context, integration string) (string, error)

// ResetAuthPreference resets cluster auth preference to defaults.
ResetAuthPreference(ctx context.Context) error

Expand Down
52 changes: 52 additions & 0 deletions lib/auth/integration/integrationv1/azureoidc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Teleport
* Copyright (C) 2025 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package integrationv1

import (
"context"

"github.com/gravitational/trace"

integrationpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/authz"
"github.com/gravitational/teleport/lib/integrations/azureoidc"
)

// GenerateAzureOIDCToken generates a token to be used to execute an Azure OIDC Integration action.
func (s *Service) GenerateAzureOIDCToken(ctx context.Context, req *integrationpb.GenerateAzureOIDCTokenRequest) (*integrationpb.GenerateAzureOIDCTokenResponse, error) {
authCtx, err := s.authorizer.Authorize(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
_, err = s.cache.GetIntegration(ctx, req.Integration)
if err != nil {
return nil, trace.Wrap(err)
}
for _, allowedRole := range []types.SystemRole{types.RoleDiscovery, types.RoleAuth, types.RoleProxy} {
if authz.HasBuiltinRole(*authCtx, string(allowedRole)) {
token, err := azureoidc.GenerateEntraOIDCToken(ctx, s.cache, s.keyStoreManager, s.clock)
if err != nil {
return nil, trace.Wrap(err)
}
return &integrationpb.GenerateAzureOIDCTokenResponse{Token: token}, nil
}
}
return nil, trace.AccessDenied("token generation is only available to auth, proxy or discovery services")
}
113 changes: 113 additions & 0 deletions lib/auth/integration/integrationv1/azureoidc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Teleport
* Copyright (C) 2025 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package integrationv1

import (
"testing"

"github.com/gravitational/trace"
"github.com/stretchr/testify/require"

integrationv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/utils/keys"
"github.com/gravitational/teleport/lib/authz"
"github.com/gravitational/teleport/lib/jwt"
"github.com/gravitational/teleport/lib/tlsca"
)

func TestGenerateAzureOIDCToken(t *testing.T) {
t.Parallel()
clusterName := "test-cluster"
integrationName := "my-integration"

publicURL := "https://example.com"

ca := newCertAuthority(t, types.HostCA, clusterName)
ctx, localClient, resourceSvc := initSvc(t, ca, clusterName, publicURL)

// Create integration
ig, err := types.NewIntegrationAzureOIDC(
types.Metadata{Name: integrationName},
&types.AzureOIDCIntegrationSpecV1{
TenantID: "foo",
ClientID: "bar",
},
)
require.NoError(t, err)
_, err = localClient.CreateIntegration(ctx, ig)
require.NoError(t, err)

t.Run("only Auth, Discovery, and Proxy roles should be able to generate Azure tokens", func(t *testing.T) {
// A dummy user should not be able to generate Azure OIDC tokens
ctx = authorizerForDummyUser(t, ctx, types.RoleSpecV6{
Allow: types.RoleConditions{Rules: []types.Rule{
{Resources: []string{types.KindIntegration}, Verbs: []string{types.VerbUse}},
}},
}, localClient)
_, err = resourceSvc.GenerateAzureOIDCToken(ctx, &integrationv1.GenerateAzureOIDCTokenRequest{Integration: integrationName})
require.True(t, trace.IsAccessDenied(err), "expected AccessDenied error, got %T", err)

// Auth, Discovery, and Proxy roles should be able to generate Azure OIDC tokens
for _, allowedRole := range []types.SystemRole{types.RoleAuth, types.RoleDiscovery, types.RoleProxy} {
ctx = authz.ContextWithUser(ctx, authz.BuiltinRole{
Role: types.RoleInstance,
AdditionalSystemRoles: []types.SystemRole{allowedRole},
Username: string(allowedRole),
Identity: tlsca.Identity{
Username: string(allowedRole),
},
})

_, err := resourceSvc.GenerateAzureOIDCToken(ctx, &integrationv1.GenerateAzureOIDCTokenRequest{Integration: integrationName})
require.NoError(t, err)
}
})

t.Run("validate the Azure token", func(t *testing.T) {
ctx = authz.ContextWithUser(ctx, authz.BuiltinRole{
Role: types.RoleInstance,
AdditionalSystemRoles: []types.SystemRole{types.RoleDiscovery},
Username: string(types.RoleDiscovery),
Identity: tlsca.Identity{
Username: string(types.RoleDiscovery),
},
})
resp, err := resourceSvc.GenerateAzureOIDCToken(ctx, &integrationv1.GenerateAzureOIDCTokenRequest{
Integration: integrationName,
})
require.NoError(t, err)

// Validate JWT against public key
require.NotEmpty(t, ca.GetActiveKeys().JWT)
jwtPubKey := ca.GetActiveKeys().JWT[0].PublicKey
publicKey, err := keys.ParsePublicKey(jwtPubKey)
require.NoError(t, err)
key, err := jwt.New(&jwt.Config{
ClusterName: clusterName,
Clock: resourceSvc.clock,
PublicKey: publicKey,
})
require.NoError(t, err)

// Verify the Azure token using the JWT
_, err = key.VerifyAzureToken(resp.Token)
require.NoError(t, err)
})
}
2 changes: 2 additions & 0 deletions lib/services/integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ type IntegrationsGetter interface {
type IntegrationsTokenGenerator interface {
// GenerateAWSOIDCToken generates a token to be used to execute an AWS OIDC Integration action.
GenerateAWSOIDCToken(ctx context.Context, integration string) (string, error)
// GenerateAzureOIDCToken generates a token to be used to execute an Azure OIDC Integration action.
GenerateAzureOIDCToken(ctx context.Context, integration string) (string, error)
}

// MarshalIntegration marshals the Integration resource to JSON.
Expand Down
1 change: 1 addition & 0 deletions lib/srv/discovery/access_graph_azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ func (s *Server) accessGraphAzureFetchersFromMatchers(
SubscriptionID: matcher.SubscriptionID,
Integration: matcher.Integration,
DiscoveryConfigName: discoveryConfigName,
OIDCCredentials: s.AccessPoint,
}
fetcher, err := azuresync.NewFetcher(fetcherCfg, s.ctx)
if err != nil {
Expand Down
Loading

0 comments on commit bc3a767

Please sign in to comment.