Skip to content

Commit

Permalink
GitHub Proxy part 3: gen github user cert and export CA (#49396)
Browse files Browse the repository at this point in the history
* GitHub Proxy part 3: gen github user cert and export CA

* address pr comment

* minor refactor

* use cache

* fix build and cache
  • Loading branch information
greedy52 authored Dec 3, 2024
1 parent b7a16e5 commit 903fee0
Show file tree
Hide file tree
Showing 21 changed files with 1,371 additions and 152 deletions.
5 changes: 5 additions & 0 deletions api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5196,3 +5196,8 @@ func (c *Client) IdentityCenterClient() identitycenterv1.IdentityCenterServiceCl
func (c *Client) ProvisioningServiceClient() provisioningv1.ProvisioningServiceClient {
return provisioningv1.NewProvisioningServiceClient(c.conn)
}

// IntegrationsClient returns integrations client.
func (c *Client) IntegrationsClient() integrationpb.IntegrationServiceClient {
return c.integrationsClient()
}
445 changes: 367 additions & 78 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.

42 changes: 42 additions & 0 deletions api/proto/teleport/integration/v1/integration_service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ syntax = "proto3";

package teleport.integration.v1;

import "google/protobuf/duration.proto";
import "google/protobuf/empty.proto";
import "teleport/legacy/types/types.proto";

Expand Down Expand Up @@ -44,6 +45,12 @@ service IntegrationService {

// GenerateAWSOIDCToken generates a token to be used when executing an AWS OIDC Integration action.
rpc GenerateAWSOIDCToken(GenerateAWSOIDCTokenRequest) returns (GenerateAWSOIDCTokenResponse);

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

// ExportIntegrationCertAuthorities exports cert authorities for an integration.
rpc ExportIntegrationCertAuthorities(ExportIntegrationCertAuthoritiesRequest) returns (ExportIntegrationCertAuthoritiesResponse);
}

// ListIntegrationsRequest is a request for a paginated list of Integrations.
Expand Down Expand Up @@ -111,3 +118,38 @@ message GenerateAWSOIDCTokenResponse {
// 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 {
// Integration is the name of the integration;
string integration = 1;
// PublicKey is the public key to be signed.
bytes public_key = 2;
// UserId is the GitHub user id.
string user_id = 3;
// KeyId is the certificate ID, usually the Teleport username.
string key_id = 4;
// Ttl is the duration the certificate will be valid for.
google.protobuf.Duration ttl = 5;
}

// GenerateGitHubUserCertResponse contains a signed certificate.
message GenerateGitHubUserCertResponse {
// AuthorizedKey is the signed certificate.
bytes authorized_key = 1;
}

// ExportIntegrationCertAuthoritiesRequest is the request to export cert
// authorities for an integration.
message ExportIntegrationCertAuthoritiesRequest {
// Integration is the name of the integration;
string integration = 1;
}

// ExportIntegrationCertAuthoritiesResponse is the response to
// ExportIntegrationCertAuthorities.
message ExportIntegrationCertAuthoritiesResponse {
// CertAuthorities are the CA key sets used to sign any new certificates.
types.CAKeySet cert_authorities = 1;
}
3 changes: 3 additions & 0 deletions lib/auth/authclient/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,9 @@ type Cache interface {

// ListAccountAssignments fetches a paginated list of IdentityCenter Account Assignments
ListAccountAssignments(context.Context, int, *pagination.PageRequestToken) ([]services.IdentityCenterAccountAssignment, pagination.NextPageToken, error)

// GetPluginStaticCredentialsByLabels will get a list of plugin static credentials resource by matching labels.
GetPluginStaticCredentialsByLabels(ctx context.Context, labels map[string]string) ([]types.PluginStaticCredentials, error)
}

type NodeWrapper struct {
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 @@ -1892,6 +1892,9 @@ type ClientI interface {
// ProvisioningServiceClient returns provisioning service client.
ProvisioningServiceClient() provisioningv1.ProvisioningServiceClient

// IntegrationsClient returns integrations client.
IntegrationsClient() integrationv1.IntegrationServiceClient

// GitServerClient returns git server client.
GitServerClient() gitserverv1.GitServerServiceClient
}
107 changes: 107 additions & 0 deletions lib/auth/integration/credentials/credentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Teleport
* Copyright (C) 2024 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 credentials

import (
"context"
"maps"

"github.com/google/uuid"
"github.com/gravitational/trace"

"github.com/gravitational/teleport/api/types"
)

// Package credentials defines constants and provides helper functions for
// integration credentials.

const (
// LabelStaticCredentialsIntegration is the label used to store the
// UUID ref in the static credentials.
LabelStaticCredentialsIntegration = types.TeleportInternalLabelPrefix + types.KindIntegration
// LabelStaticCredentialsPurpose is the label used to store the purpose of
// the static credentials.
LabelStaticCredentialsPurpose = "purpose"

// PurposeGitHubSSHCA is the label value that indicates the static
// credentials contains the GitHub SSH CA.
PurposeGitHubSSHCA = "github-sshca"
// PurposeGitHubOAuth is the label value that indicates the static
// credentials contains the GitHub OAuth ID and secret.
PurposeGitHubOAuth = "github-oauth"
)

// NewRef creates a new PluginStaticCredentialsRef that is saved along with the
// integration resource in the backend. The actual credentials are saved as
// PlugStaticCredentials and can only be retrieved by the ref.
func NewRef() *types.PluginStaticCredentialsRef {
return NewRefWithUUID(uuid.NewString())
}

// NewRefWithUUID creates a PluginStaticCredentialsRef with provided UUID.
func NewRefWithUUID(uuid string) *types.PluginStaticCredentialsRef {
return &types.PluginStaticCredentialsRef{
Labels: map[string]string{
LabelStaticCredentialsIntegration: uuid,
},
}
}

// CopyRefLabels copies the labels from the Ref to the actual credentials so the
// credentials can be retrieved using the same labels.
func CopyRefLabels(cred types.PluginStaticCredentials, ref *types.PluginStaticCredentialsRef) {
labels := cred.GetStaticLabels()
if labels == nil {
labels = make(map[string]string)
}
maps.Copy(labels, ref.Labels)

cred.SetStaticLabels(labels)
}

// ByLabelsGetter defines an interface to retrieve credentials by labels.
type ByLabelsGetter interface {
// GetPluginStaticCredentialsByLabels will get a list of plugin static credentials resource by matching labels.
GetPluginStaticCredentialsByLabels(ctx context.Context, labels map[string]string) ([]types.PluginStaticCredentials, error)
}

// GetByPurpose retrieves a credentials based on the provided purpose.
func GetByPurpose(ctx context.Context, ref *types.PluginStaticCredentialsRef, purpose string, getter ByLabelsGetter) (types.PluginStaticCredentials, error) {
if ref == nil {
return nil, trace.BadParameter("missing credentials ref")
}
labels := ref.Labels
if len(labels) == 0 {
return nil, trace.BadParameter("missing labels from credentials ref")
}
labels[LabelStaticCredentialsPurpose] = purpose

creds, err := getter.GetPluginStaticCredentialsByLabels(ctx, labels)
if err != nil {
return nil, trace.Wrap(err)
}
switch len(creds) {
case 0:
return nil, trace.NotFound("%v credentials not found", purpose)
case 1:
return creds[0], nil
default:
return nil, trace.CompareFailed("expecting one plugin static credentials but got %v", len(creds))
}
}
Loading

0 comments on commit 903fee0

Please sign in to comment.