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
77 changes: 74 additions & 3 deletions api/types/integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ import (
const (
// IntegrationSubKindAWSOIDC is an integration with AWS that uses OpenID Connect as an Identity Provider.
IntegrationSubKindAWSOIDC = "aws-oidc"

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

// Integration specifies is a connection configuration between Teleport and a 3rd party system.
Expand All @@ -47,6 +50,9 @@ type Integration interface {
// SetAWSOIDCIssuerS3URI sets the IssuerS3URI of the AWS OIDC Spec.
// Eg, s3://my-bucket/my-prefix
SetAWSOIDCIssuerS3URI(string)

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

var _ ResourceWithLabels = (*IntegrationV1)(nil)
Expand All @@ -72,6 +78,27 @@ func NewIntegrationAWSOIDC(md Metadata, spec *AWSOIDCIntegrationSpecV1) (*Integr
return ig, nil
}

// NewIntegrationAzureOIDC returns a new `azure-oidc` subkind Integration
func NewIntegrationAzureOIDC(md Metadata, spec *AzureOIDCIntegrationSpecV1) (*IntegrationV1, error) {
ig := &IntegrationV1{
ResourceHeader: ResourceHeader{
Metadata: md,
Kind: KindIntegration,
Version: V1,
SubKind: IntegrationSubKindAzureOIDC,
},
Spec: IntegrationSpecV1{
SubKindSpec: &IntegrationSpecV1_AzureOIDC{
AzureOIDC: 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 @@ -128,14 +155,19 @@ func (s *IntegrationSpecV1) CheckAndSetDefaults() error {
if err != nil {
return trace.Wrap(err)
}
case *IntegrationSpecV1_AzureOIDC:
err := integrationSubKind.Validate()
if err != nil {
return trace.Wrap(err)
}
default:
return trace.BadParameter("unknown integration subkind: %T", integrationSubKind)
}

return nil
}

// CheckAndSetDefaults validates an agent mesh integration.
// CheckAndSetDefaults validates the configuration for AWS OIDC integration subkind.
func (s *IntegrationSpecV1_AWSOIDC) CheckAndSetDefaults() error {
if s == nil || s.AWSOIDC == nil {
return trace.BadParameter("aws_oidc is required for %q subkind", IntegrationSubKindAWSOIDC)
Expand All @@ -160,6 +192,21 @@ func (s *IntegrationSpecV1_AWSOIDC) CheckAndSetDefaults() error {
return nil
}

// Validate validates the configuration for Azure OIDC integration subkind.
func (s *IntegrationSpecV1_AzureOIDC) Validate() error {
if s == nil || s.AzureOIDC == nil {
return trace.BadParameter("azure_oidc is required for %q subkind", IntegrationSubKindAzureOIDC)
}
if s.AzureOIDC.TenantID == "" {
return trace.BadParameter("tenant_id must be set")
}
if s.AzureOIDC.ClientID == "" {
return trace.BadParameter("client_id must be set")
}

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 @@ -198,6 +245,11 @@ func (ig *IntegrationV1) SetAWSOIDCIssuerS3URI(issuerS3URI string) {
}
}

// GetAzureOIDCIntegrationSpec returns the specific spec fields for `azure-oidc` subkind integrations.
func (ig *IntegrationV1) GetAzureOIDCIntegrationSpec() *AzureOIDCIntegrationSpecV1 {
return ig.Spec.GetAzureOIDC()
}

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

Expand Down Expand Up @@ -247,7 +299,8 @@ func (ig *IntegrationV1) UnmarshalJSON(data []byte) error {
d := struct {
ResourceHeader `json:""`
Spec struct {
AWSOIDC json.RawMessage `json:"aws_oidc"`
AWSOIDC json.RawMessage `json:"aws_oidc"`
AzureOIDC json.RawMessage `json:"azure_oidc"`
} `json:"spec"`
}{}

Expand All @@ -270,6 +323,17 @@ func (ig *IntegrationV1) UnmarshalJSON(data []byte) error {

integration.Spec.SubKindSpec = subkindSpec

case IntegrationSubKindAzureOIDC:
subkindSpec := &IntegrationSpecV1_AzureOIDC{
AzureOIDC: &AzureOIDCIntegrationSpecV1{},
}

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

integration.Spec.SubKindSpec = subkindSpec

default:
return trace.BadParameter("invalid subkind %q", integration.ResourceHeader.SubKind)
}
Expand All @@ -290,7 +354,8 @@ func (ig *IntegrationV1) MarshalJSON() ([]byte, error) {
d := struct {
ResourceHeader `json:""`
Spec struct {
AWSOIDC AWSOIDCIntegrationSpecV1 `json:"aws_oidc"`
AWSOIDC AWSOIDCIntegrationSpecV1 `json:"aws_oidc,omitempty"`
AzureOIDC AzureOIDCIntegrationSpecV1 `json:"azure_oidc,omitempty"`
} `json:"spec"`
}{}

Expand All @@ -303,6 +368,12 @@ func (ig *IntegrationV1) MarshalJSON() ([]byte, error) {
}

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

d.Spec.AzureOIDC = *ig.GetAzureOIDCIntegrationSpec()
default:
return nil, trace.BadParameter("invalid subkind %q", ig.SubKind)
}
Expand Down
107 changes: 89 additions & 18 deletions api/types/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
)

func TestIntegrationJSONMarshalCycle(t *testing.T) {
ig, err := NewIntegrationAWSOIDC(
aws, err := NewIntegrationAWSOIDC(
Metadata{Name: "some-integration"},
&AWSOIDCIntegrationSpecV1{
RoleARN: "arn:aws:iam::123456789012:role/DevTeams",
Expand All @@ -37,14 +37,29 @@ func TestIntegrationJSONMarshalCycle(t *testing.T) {
)
require.NoError(t, err)

bs, err := json.Marshal(ig)
azure, err := NewIntegrationAzureOIDC(
Metadata{Name: "some-integration"},
&AzureOIDCIntegrationSpecV1{
TenantID: "foo-bar",
ClientID: "baz-quux",
},
)
require.NoError(t, err)

var ig2 IntegrationV1
err = json.Unmarshal(bs, &ig2)
require.NoError(t, err)
allIntegrations := []*IntegrationV1{aws, azure}

require.Equal(t, &ig2, ig)
for _, ig := range allIntegrations {
t.Run(ig.SubKind, func(t *testing.T) {
bs, err := json.Marshal(ig)
require.NoError(t, err)

var ig2 IntegrationV1
err = json.Unmarshal(bs, &ig2)
require.NoError(t, err)

require.Equal(t, &ig2, ig)
})
}
}

func TestIntegrationCheckAndSetDefaults(t *testing.T) {
Expand All @@ -59,7 +74,7 @@ func TestIntegrationCheckAndSetDefaults(t *testing.T) {
expectedErrorIs func(error) bool
}{
{
name: "valid",
name: "aws-oidc: valid",
integration: func(name string) (*IntegrationV1, error) {
return NewIntegrationAWSOIDC(
Metadata{
Expand Down Expand Up @@ -104,9 +119,7 @@ func TestIntegrationCheckAndSetDefaults(t *testing.T) {
nil,
)
},
expectedErrorIs: func(err error) bool {
return trace.IsBadParameter(err)
},
expectedErrorIs: trace.IsBadParameter,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

},
{
name: "aws-oidc: error when issuer is not a valid url",
Expand All @@ -121,9 +134,7 @@ func TestIntegrationCheckAndSetDefaults(t *testing.T) {
},
)
},
expectedErrorIs: func(err error) bool {
return trace.IsBadParameter(err)
},
expectedErrorIs: trace.IsBadParameter,
},
{
name: "aws-oidc: issuer is not an s3 url",
Expand All @@ -138,9 +149,7 @@ func TestIntegrationCheckAndSetDefaults(t *testing.T) {
},
)
},
expectedErrorIs: func(err error) bool {
return trace.IsBadParameter(err)
},
expectedErrorIs: trace.IsBadParameter,
},
{
name: "aws-oidc: error when no role is provided",
Expand All @@ -152,9 +161,71 @@ func TestIntegrationCheckAndSetDefaults(t *testing.T) {
&AWSOIDCIntegrationSpecV1{},
)
},
expectedErrorIs: func(err error) bool {
return trace.IsBadParameter(err)
expectedErrorIs: trace.IsBadParameter,
},
{
name: "azure-oidc: valid",
integration: func(name string) (*IntegrationV1, error) {
return NewIntegrationAzureOIDC(
Metadata{
Name: name,
},
&AzureOIDCIntegrationSpecV1{
ClientID: "baz-quux",
TenantID: "foo-bar",
},
)
},
expectedIntegration: func(name string) *IntegrationV1 {
return &IntegrationV1{
ResourceHeader: ResourceHeader{
Kind: KindIntegration,
SubKind: IntegrationSubKindAzureOIDC,
Version: V1,
Metadata: Metadata{
Name: name,
Namespace: defaults.Namespace,
},
},
Spec: IntegrationSpecV1{
SubKindSpec: &IntegrationSpecV1_AzureOIDC{
AzureOIDC: &AzureOIDCIntegrationSpecV1{
ClientID: "baz-quux",
TenantID: "foo-bar",
},
},
},
}
},
expectedErrorIs: noErrorFunc,
},
{
name: "azure-oidc: error when no tenant id is provided",
integration: func(name string) (*IntegrationV1, error) {
return NewIntegrationAzureOIDC(
Metadata{
Name: name,
},
&AzureOIDCIntegrationSpecV1{
ClientID: "baz-quux",
},
)
},
expectedErrorIs: trace.IsBadParameter,
},
{
name: "azure-oidc: error when no client id is provided",
integration: func(name string) (*IntegrationV1, error) {
return NewIntegrationAzureOIDC(
Metadata{
Name: name,
},
&AzureOIDCIntegrationSpecV1{
TenantID: "foo-bar",
},
)
},
expectedErrorIs: trace.IsBadParameter,
},
} {
t.Run(tt.name, func(t *testing.T) {
Expand Down
23 changes: 23 additions & 0 deletions api/types/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ var AllPluginTypes = []PluginType{
PluginTypePagerDuty,
PluginTypeMattermost,
PluginTypeDiscord,
PluginTypeEntraID,
}

const (
Expand Down Expand Up @@ -66,6 +67,8 @@ const (
PluginTypeDiscord = "discord"
// PluginTypeGitlab indicates the Gitlab access plugin
PluginTypeGitlab = "gitlab"
// PluginTypeEntraID indicates the Entra ID sync plugin
PluginTypeEntraID = "entra-id"
)

// PluginSubkind represents the type of the plugin, e.g., access request, MDM etc.
Expand Down Expand Up @@ -294,6 +297,13 @@ func (p *PluginV1) CheckAndSetDefaults() error {
if staticCreds == nil {
return trace.BadParameter("Gitlab plugin must be used with the static credentials ref type")
}
case *PluginSpecV1_EntraId:
if settings.EntraId == nil {
return trace.BadParameter("missing Entra ID settings")
}
if err := settings.EntraId.Validate(); err != nil {
return trace.Wrap(err)
}
default:
return trace.BadParameter("settings are not set or have an unknown type")
}
Expand Down Expand Up @@ -459,6 +469,8 @@ func (p *PluginV1) GetType() PluginType {
return PluginTypeServiceNow
case *PluginSpecV1_Gitlab:
return PluginTypeGitlab
case *PluginSpecV1_EntraId:
return PluginTypeEntraID
default:
return PluginTypeUnknown
}
Expand Down Expand Up @@ -609,6 +621,17 @@ func (c *PluginOAuth2AccessTokenCredentials) CheckAndSetDefaults() error {
return nil
}

func (c *PluginEntraIDSettings) Validate() error {
if c.SyncSettings == nil {
return trace.BadParameter("sync_settings must be set")
}
if len(c.SyncSettings.DefaultOwners) == 0 {
return trace.BadParameter("sync_settings.default_owners must be set")
}

return nil
}

// GetCode returns the status code
func (c PluginStatusV1) GetCode() PluginStatusCode {
return c.Code
Expand Down
Loading