Skip to content

Commit

Permalink
use static credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
greedy52 committed Nov 22, 2024
1 parent 6501d5d commit e447d53
Show file tree
Hide file tree
Showing 13 changed files with 1,526 additions and 1,145 deletions.
31 changes: 14 additions & 17 deletions api/proto/teleport/legacy/types/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6994,6 +6994,7 @@ message PluginStaticCredentialsSpecV1 {
string APIToken = 1;
PluginStaticCredentialsBasicAuth BasicAuth = 2;
PluginStaticCredentialsOAuthClientSecret OAuthClientSecret = 3;
PluginStaticCredentialsSSHCertAuthorities SSHCertAuthorities = 4;
}
}

Expand All @@ -7015,6 +7016,14 @@ message PluginStaticCredentialsOAuthClientSecret {
string ClientSecret = 2 [(gogoproto.jsontag) = "client_secret"];
}

// PluginStaticCredentialsSSHCertAuthorities contains the active SSH CAs used
// for the integration or plugin.
message PluginStaticCredentialsSSHCertAuthorities {
// CertAuthorities contains the active SSH CAs used for the integration or
// plugin.
repeated SSHKeyPair cert_authorities = 1;
}

// SAMLIdPServiceProviderV1 is the representation of a SAML IdP service provider.
message SAMLIdPServiceProviderV1 {
option (gogoproto.goproto_stringer) = false;
Expand Down Expand Up @@ -7365,6 +7374,9 @@ message IntegrationSpecV1 {
// GitHub contains the specific fields to handle the GitHub integration subkind.
GitHubIntegrationSpecV1 GitHub = 3 [(gogoproto.jsontag) = "github,omitempty"];
}

// Credentials contains credentials for the integration.
PluginCredentialsV1 credentials = 4;
}

// AWSOIDCIntegrationSpecV1 contains the spec properties for the AWS OIDC SubKind Integration.
Expand Down Expand Up @@ -7413,27 +7425,12 @@ message AzureOIDCIntegrationSpecV1 {
message GitHubIntegrationSpecV1 {
// Organization specifies the name of the organization for the GitHub integration.
string Organization = 1 [(gogoproto.jsontag) = "organization,omitempty"];

// Proxy specifies GitHub proxy related settings.
GitHubProxy proxy = 2 [(gogoproto.jsontag) = "proxy,omitempty"];
}

// GitHubProxy specifies GitHub proxy related settings.
message GitHubProxy {
// CertAuthority contains the active SSH CAs used between Teleport and
// GitHub. GitHub does not allow the same CA to be used for a different
// organization so the CA is defined per integration. TODO(greedy52) support
// rotation, HSM encryption.
repeated SSHKeyPair cert_authorities = 1 [(gogoproto.jsontag) = "cert_authorities,omitempty"];

// Connector specifies the GitHub connector spec used to obtain user ID and
// username.
GitHubProxyConnector connector = 2 [(gogoproto.jsontag) = "connector,omitempty"];
}

// GitHubProxyConnector specifies the GitHub connector spec for a GitHub proxy.
// This type is a subset of GithubConnectorSpecV3 but does not require fields
// like TeamRolesMapping.
// like TeamRolesMapping. ClientID and ClientSecret are defined for object
// creation request and then will be saved in static credentials instead.
message GitHubProxyConnector {
// ClientID is the Github OAuth app client ID.
string ClientID = 1 [(gogoproto.jsontag) = "client_id"];
Expand Down
124 changes: 60 additions & 64 deletions api/types/integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"net/url"

"github.com/gravitational/trace"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/protoadapt"

"github.com/gravitational/teleport/api/utils"
)
Expand Down Expand Up @@ -70,8 +72,12 @@ type Integration interface {
// SetGitHubIntegrationSpec returns the GitHub spec.
SetGitHubIntegrationSpec(*GitHubIntegrationSpecV1)

// WithoutSecrets returns an instance of resource without secrets.
WithoutSecrets() Integration
// SetCredentials updates credentials.
SetCredentials(creds PluginCredentials) error
// GetCredentials retrieves credentials.
GetCredentials() PluginCredentials
// WithoutCredentials returns a copy without credentials.
WithoutCredentials() Integration
}

var _ ResourceWithLabels = (*IntegrationV1)(nil)
Expand Down Expand Up @@ -272,40 +278,8 @@ func (s *IntegrationSpecV1_GitHub) CheckAndSetDefaults() error {
if s == nil || s.GitHub == nil {
return trace.BadParameter("github is required for %q subkind", IntegrationSubKindGitHub)
}
if s.GitHub.Organization == "" {
return trace.BadParameter("organization must be set")
}
if s.GitHub.Proxy == nil {
s.GitHub.Proxy = &GitHubProxy{}
}
if err := s.GitHub.Proxy.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
return nil
}

// CheckAndSetDefaults validates the configuration for GitHub proxy settings.
func (p *GitHubProxy) CheckAndSetDefaults() error {
for _, ca := range p.CertAuthorities {
if err := ca.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
}
if p.Connector != nil {
if err := p.Connector.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
}
return nil
}

// CheckAndSetDefaults validates the configuration for GitHub Proxy connector.
func (c *GitHubProxyConnector) CheckAndSetDefaults() error {
if c.ClientID == "" {
return trace.BadParameter("client_id must be set")
}
if c.RedirectURL == "" {
return trace.BadParameter("redirect must be set")
if err := ValidateGitHubOrganizationName(s.GitHub.Organization); err != nil {
return trace.Wrap(err, "invalid organization name")
}
return nil
}
Expand Down Expand Up @@ -414,9 +388,10 @@ func (ig *IntegrationV1) UnmarshalJSON(data []byte) error {
d := struct {
ResourceHeader `json:""`
Spec struct {
AWSOIDC json.RawMessage `json:"aws_oidc"`
AzureOIDC json.RawMessage `json:"azure_oidc"`
GitHub json.RawMessage `json:"github"`
AWSOIDC json.RawMessage `json:"aws_oidc"`
AzureOIDC json.RawMessage `json:"azure_oidc"`
GitHub json.RawMessage `json:"github"`
Credentials json.RawMessage `json:"credentials"`
} `json:"spec"`
}{}

Expand All @@ -426,6 +401,13 @@ func (ig *IntegrationV1) UnmarshalJSON(data []byte) error {
}

integration.ResourceHeader = d.ResourceHeader
if len(d.Spec.Credentials) != 0 {
var credentials PluginCredentialsV1
if err := protojson.Unmarshal(d.Spec.Credentials, protoadapt.MessageV2Of(&credentials)); err != nil {
return trace.Wrap(err)
}
integration.Spec.Credentials = &credentials
}

switch integration.SubKind {
case IntegrationSubKindAWSOIDC:
Expand Down Expand Up @@ -481,13 +463,21 @@ func (ig *IntegrationV1) MarshalJSON() ([]byte, error) {
d := struct {
ResourceHeader `json:""`
Spec struct {
AWSOIDC AWSOIDCIntegrationSpecV1 `json:"aws_oidc,omitempty"`
AzureOIDC AzureOIDCIntegrationSpecV1 `json:"azure_oidc,omitempty"`
GitHub GitHubIntegrationSpecV1 `json:"github,omitempty"`
AWSOIDC AWSOIDCIntegrationSpecV1 `json:"aws_oidc,omitempty"`
AzureOIDC AzureOIDCIntegrationSpecV1 `json:"azure_oidc,omitempty"`
GitHub GitHubIntegrationSpecV1 `json:"github,omitempty"`
Credentials json.RawMessage `json:"credentials,omitempty"`
} `json:"spec"`
}{}

d.ResourceHeader = ig.ResourceHeader
if ig.Spec.Credentials != nil {
data, err := protojson.Marshal(protoadapt.MessageV2Of(ig.Spec.Credentials))
if err != nil {
return nil, trace.Wrap(err)
}
d.Spec.Credentials = json.RawMessage(data)
}

switch ig.SubKind {
case IntegrationSubKindAWSOIDC:
Expand Down Expand Up @@ -515,30 +505,36 @@ func (ig *IntegrationV1) MarshalJSON() ([]byte, error) {
return out, trace.Wrap(err)
}

// Copy returns a deep copy of the integration.
func (ig *IntegrationV1) Copy() *IntegrationV1 {
return utils.CloneProtoMsg(ig)
// SetCredentials updates credentials.
func (ig *IntegrationV1) SetCredentials(creds PluginCredentials) error {
if creds == nil {
ig.Spec.Credentials = nil
return nil
}
switch creds := creds.(type) {
case *PluginCredentialsV1:
ig.Spec.Credentials = creds
default:
return trace.BadParameter("unsupported plugin credential type %T", creds)
}
return nil
}

// WithoutSecrets returns an instance of resource without secrets.
func (ig *IntegrationV1) WithoutSecrets() Integration {
switch ig.SubKind {
case IntegrationSubKindGitHub:
spec := ig.GetGitHubIntegrationSpec()
if spec == nil || spec.Proxy == nil {
return ig
}
// GetCredentials retrieves credentials.
func (ig *IntegrationV1) GetCredentials() PluginCredentials {
if ig.Spec.Credentials == nil {
return nil
}
return ig.Spec.Credentials
}

clone := ig.Copy()
spec = clone.GetGitHubIntegrationSpec()
for i := range spec.Proxy.CertAuthorities {
spec.Proxy.CertAuthorities[i].PrivateKey = nil
}
if spec.Proxy.Connector != nil {
spec.Proxy.Connector.ClientSecret = ""
}
clone.SetGitHubIntegrationSpec(spec)
return clone
// WithoutCredentials returns a copy without credentials.
func (ig *IntegrationV1) WithoutCredentials() Integration {
if ig == nil || ig.GetCredentials() == nil {
return ig
}
return ig

clone := utils.CloneProtoMsg(ig)
clone.SetCredentials(nil)
return clone
}
32 changes: 32 additions & 0 deletions api/types/integration_github.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
Copyright 2024 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 types

import "regexp"

// validGitHubOrganizationName filters the allowed characters in GitHub
// organization name.
//
// GitHub shows the following error when inputing an invalid org name:
// The name '_' may only contain alphanumeric characters or single hyphens, and
// cannot begin or end with a hyphen.
var validGitHubOrganizationName = regexp.MustCompile(`^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?$`)

// ValidateGitHubOrganizationName returns an error if a given string is not a
// valid GitHub organization name.
func ValidateGitHubOrganizationName(name string) error {
return ValidateResourceName(validGitHubOrganizationName, name)
}
60 changes: 60 additions & 0 deletions api/types/integration_github_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
Copyright 2024 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 types

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestValidateGitHubOrganizationName(t *testing.T) {
tests := []struct {
name string
checkError require.ErrorAssertionFunc
}{
{
name: "valid-org",
checkError: require.NoError,
},
{
name: "a",
checkError: require.NoError,
},
{
name: "1-valid-start-with-digit",
checkError: require.NoError,
},
{
name: "-invalid-start-with-hyphen",
checkError: require.Error,
},
{
name: "invalid-end-with-hyphen-",
checkError: require.Error,
},
{
name: "invalid charactersc",
checkError: require.Error,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
test.checkError(t, ValidateGitHubOrganizationName(test.name))
})
}
}
Loading

0 comments on commit e447d53

Please sign in to comment.