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
8 changes: 8 additions & 0 deletions api/proto/teleport/legacy/types/events/events.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4330,6 +4330,8 @@ message IntegrationMetadata {
AWSOIDCIntegrationMetadata AWSOIDC = 2 [(gogoproto.jsontag) = "aws_oidc,omitempty"];
// AzureOIDC contains metadata for Azure OIDC integrations.
AzureOIDCIntegrationMetadata AzureOIDC = 3 [(gogoproto.jsontag) = "azure_oidc,omitempty"];
// GitHub contains metadata for GitHub integrations.
GitHubIntegrationMetadata GitHub = 4 [(gogoproto.jsontag) = "github,omitempty"];
}

// AWSOIDCIntegrationMetadata contains metadata for AWS OIDC integrations.
Expand All @@ -4351,6 +4353,12 @@ message AzureOIDCIntegrationMetadata {
string ClientID = 2 [(gogoproto.jsontag) = "client_id,omitempty"];
}

// GitHubIntegrationMetadata contains metadata for GitHub integrations.
message GitHubIntegrationMetadata {
// Organization specifies the name of the organization for the GitHub integration.
string Organization = 1 [(gogoproto.jsontag) = "organization,omitempty"];
}

// PluginCreate is emitted when a plugin resource is created.
message PluginCreate {
// Metadata is a common event metadata.
Expand Down
20 changes: 20 additions & 0 deletions api/proto/teleport/legacy/types/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -7119,6 +7119,7 @@ message PluginStaticCredentialsSpecV1 {
string APIToken = 1;
PluginStaticCredentialsBasicAuth BasicAuth = 2;
PluginStaticCredentialsOAuthClientSecret OAuthClientSecret = 3;
PluginStaticCredentialsSSHCertAuthorities SSHCertAuthorities = 4;
}
}

Expand All @@ -7140,6 +7141,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;
Comment thread
greedy52 marked this conversation as resolved.
}

// SAMLIdPServiceProviderV1 is the representation of a SAML IdP service provider.
message SAMLIdPServiceProviderV1 {
option (gogoproto.goproto_stringer) = false;
Expand Down Expand Up @@ -7487,7 +7496,12 @@ message IntegrationSpecV1 {
AWSOIDCIntegrationSpecV1 AWSOIDC = 1 [(gogoproto.jsontag) = "aws_oidc,omitempty"];
// AzureOIDC contains the specific fields to handle the Azure OIDC Integration subkind
AzureOIDCIntegrationSpecV1 AzureOIDC = 2 [(gogoproto.jsontag) = "azure_oidc,omitempty"];
// 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 @@ -7532,6 +7546,12 @@ message AzureOIDCIntegrationSpecV1 {
string ClientID = 2 [(gogoproto.jsontag) = "client_id,omitempty"];
}

// GitHubIntegrationSpecV1 contains the specific fields to handle the GitHub integration subkind.
message GitHubIntegrationSpecV1 {
// Organization specifies the name of the organization for the GitHub integration.
string Organization = 1 [(gogoproto.jsontag) = "organization,omitempty"];
}

// HeadlessAuthentication holds data for an ongoing headless authentication attempt.
message HeadlessAuthentication {
// Header is the resource header.
Expand Down
2,575 changes: 1,404 additions & 1,171 deletions api/types/events/events.pb.go

Large diffs are not rendered by default.

147 changes: 141 additions & 6 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 All @@ -32,6 +34,9 @@ const (

// IntegrationSubKindAzureOIDC is an integration with Azure that uses OpenID Connect as an Identity Provider.
IntegrationSubKindAzureOIDC = "azure-oidc"

// IntegrationSubKindGitHub is an integration with GitHub.
IntegrationSubKindGitHub = "github"
)

const (
Expand Down Expand Up @@ -61,6 +66,18 @@ type Integration interface {

// GetAzureOIDCIntegrationSpec returns the `azure-oidc` spec fields.
GetAzureOIDCIntegrationSpec() *AzureOIDCIntegrationSpecV1

// GetGitHubIntegrationSpec returns the GitHub spec.
GetGitHubIntegrationSpec() *GitHubIntegrationSpecV1
// SetGitHubIntegrationSpec returns the GitHub spec.
SetGitHubIntegrationSpec(*GitHubIntegrationSpecV1)

// 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 @@ -107,6 +124,27 @@ func NewIntegrationAzureOIDC(md Metadata, spec *AzureOIDCIntegrationSpecV1) (*In
return ig, nil
}

// NewIntegrationGitHub returns a new `github` subkind Integration
func NewIntegrationGitHub(md Metadata, spec *GitHubIntegrationSpecV1) (*IntegrationV1, error) {
ig := &IntegrationV1{
ResourceHeader: ResourceHeader{
Metadata: md,
Kind: KindIntegration,
Version: V1,
SubKind: IntegrationSubKindGitHub,
},
Spec: IntegrationSpecV1{
SubKindSpec: &IntegrationSpecV1_GitHub{
GitHub: spec,
},
},
}
if err := ig.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
return ig, nil
}

// String returns the integration string representation.
func (ig *IntegrationV1) String() string {
return fmt.Sprintf("IntegrationV1(Name=%v, SubKind=%s, Labels=%v)",
Expand Down Expand Up @@ -168,6 +206,11 @@ func (s *IntegrationSpecV1) CheckAndSetDefaults() error {
if err != nil {
return trace.Wrap(err)
}
case *IntegrationSpecV1_GitHub:
if err := integrationSubKind.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
return nil
default:
return trace.BadParameter("unknown integration subkind: %T", integrationSubKind)
}
Expand Down Expand Up @@ -230,6 +273,17 @@ func (s *IntegrationSpecV1_AzureOIDC) Validate() error {
return nil
}

// CheckAndSetDefaults validates the configuration for GitHub integration subkind.
func (s *IntegrationSpecV1_GitHub) CheckAndSetDefaults() error {
if s == nil || s.GitHub == nil {
return trace.BadParameter("github spec must be set for GitHub integrations")
}
if err := ValidateGitHubOrganizationName(s.GitHub.Organization); err != nil {
return trace.Wrap(err, "invalid GitHub organization name")
}
return nil
}

// GetAWSOIDCIntegrationSpec returns the specific spec fields for `aws-oidc` subkind integrations.
func (ig *IntegrationV1) GetAWSOIDCIntegrationSpec() *AWSOIDCIntegrationSpecV1 {
return ig.Spec.GetAWSOIDC()
Expand Down Expand Up @@ -273,6 +327,18 @@ func (ig *IntegrationV1) GetAzureOIDCIntegrationSpec() *AzureOIDCIntegrationSpec
return ig.Spec.GetAzureOIDC()
}

// GetGitHubIntegrationSpec returns the GitHub spec.
func (ig *IntegrationV1) GetGitHubIntegrationSpec() *GitHubIntegrationSpecV1 {
return ig.Spec.GetGitHub()
}

// SetGitHubIntegrationSpec returns the GitHub spec.
func (ig *IntegrationV1) SetGitHubIntegrationSpec(spec *GitHubIntegrationSpecV1) {
ig.Spec.SubKindSpec = &IntegrationSpecV1_GitHub{
GitHub: spec,
}
}

// Integrations is a list of Integration resources.
type Integrations []Integration

Expand Down Expand Up @@ -322,8 +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"`
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 @@ -333,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 {
Comment thread
greedy52 marked this conversation as resolved.
return trace.Wrap(err)
}
integration.Spec.Credentials = &credentials
}

switch integration.SubKind {
case IntegrationSubKindAWSOIDC:
Expand All @@ -357,6 +432,17 @@ func (ig *IntegrationV1) UnmarshalJSON(data []byte) error {

integration.Spec.SubKindSpec = subkindSpec

case IntegrationSubKindGitHub:
subkindSpec := &IntegrationSpecV1_GitHub{
GitHub: &GitHubIntegrationSpecV1{},
}

if err := json.Unmarshal(d.Spec.GitHub, subkindSpec.GitHub); err != nil {
return trace.Wrap(err)
}

integration.Spec.SubKindSpec = subkindSpec

default:
return trace.BadParameter("invalid subkind %q", integration.ResourceHeader.SubKind)
}
Expand All @@ -377,30 +463,79 @@ func (ig *IntegrationV1) MarshalJSON() ([]byte, error) {
d := struct {
ResourceHeader `json:""`
Spec struct {
AWSOIDC AWSOIDCIntegrationSpecV1 `json:"aws_oidc,omitempty"`
AzureOIDC AzureOIDCIntegrationSpecV1 `json:"azure_oidc,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:
if ig.GetAWSOIDCIntegrationSpec() == nil {
return nil, trace.BadParameter("missing subkind data for %q subkind", ig.SubKind)
return nil, trace.BadParameter("missing spec for %q subkind", ig.SubKind)
}

d.Spec.AWSOIDC = *ig.GetAWSOIDCIntegrationSpec()
case IntegrationSubKindAzureOIDC:
if ig.GetAzureOIDCIntegrationSpec() == nil {
return nil, trace.BadParameter("missing subkind data for %q subkind", ig.SubKind)
return nil, trace.BadParameter("missing spec for %q subkind", ig.SubKind)
}

d.Spec.AzureOIDC = *ig.GetAzureOIDCIntegrationSpec()
case IntegrationSubKindGitHub:
if ig.GetGitHubIntegrationSpec() == nil {
return nil, trace.BadParameter("missing spec for %q subkind", ig.SubKind)
}
d.Spec.GitHub = *ig.GetGitHubIntegrationSpec()
default:
return nil, trace.BadParameter("invalid subkind %q", ig.SubKind)
}

out, err := json.Marshal(d)
return out, trace.Wrap(err)
}

// 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
}

// GetCredentials retrieves credentials.
func (ig *IntegrationV1) GetCredentials() PluginCredentials {
// This function returns an interface so return nil explicitly.
if ig.Spec.Credentials == nil {
return nil
}
return ig.Spec.Credentials
Comment thread
greedy52 marked this conversation as resolved.
}

// WithoutCredentials returns a copy without credentials.
func (ig *IntegrationV1) WithoutCredentials() Integration {
if ig == nil || ig.GetCredentials() == nil {
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)
}
Loading