github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/types/integration.go (about)

     1  /*
     2  Copyright 2023 Gravitational, Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package types
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"net/url"
    23  
    24  	"github.com/gravitational/trace"
    25  
    26  	"github.com/gravitational/teleport/api/utils"
    27  )
    28  
    29  const (
    30  	// IntegrationSubKindAWSOIDC is an integration with AWS that uses OpenID Connect as an Identity Provider.
    31  	IntegrationSubKindAWSOIDC = "aws-oidc"
    32  
    33  	// IntegrationSubKindAzureOIDC is an integration with Azure that uses OpenID Connect as an Identity Provider.
    34  	IntegrationSubKindAzureOIDC = "azure-oidc"
    35  )
    36  
    37  // Integration specifies is a connection configuration between Teleport and a 3rd party system.
    38  type Integration interface {
    39  	ResourceWithLabels
    40  
    41  	// CanChangeStateTo checks if the current Integration can be updated for the provided integration.
    42  	CanChangeStateTo(Integration) error
    43  
    44  	// GetAWSOIDCIntegrationSpec returns the `aws-oidc` spec fields.
    45  	GetAWSOIDCIntegrationSpec() *AWSOIDCIntegrationSpecV1
    46  	// SetAWSOIDCIntegrationSpec sets the `aws-oidc` spec fields.
    47  	SetAWSOIDCIntegrationSpec(*AWSOIDCIntegrationSpecV1)
    48  	// SetAWSOIDCRoleARN sets the RoleARN of the AWS OIDC Spec.
    49  	SetAWSOIDCRoleARN(string)
    50  	// SetAWSOIDCIssuerS3URI sets the IssuerS3URI of the AWS OIDC Spec.
    51  	// Eg, s3://my-bucket/my-prefix
    52  	SetAWSOIDCIssuerS3URI(string)
    53  
    54  	// GetAzureOIDCIntegrationSpec returns the `azure-oidc` spec fields.
    55  	GetAzureOIDCIntegrationSpec() *AzureOIDCIntegrationSpecV1
    56  }
    57  
    58  var _ ResourceWithLabels = (*IntegrationV1)(nil)
    59  
    60  // NewIntegrationAWSOIDC returns a new `aws-oidc` subkind Integration
    61  func NewIntegrationAWSOIDC(md Metadata, spec *AWSOIDCIntegrationSpecV1) (*IntegrationV1, error) {
    62  	ig := &IntegrationV1{
    63  		ResourceHeader: ResourceHeader{
    64  			Metadata: md,
    65  			Kind:     KindIntegration,
    66  			Version:  V1,
    67  			SubKind:  IntegrationSubKindAWSOIDC,
    68  		},
    69  		Spec: IntegrationSpecV1{
    70  			SubKindSpec: &IntegrationSpecV1_AWSOIDC{
    71  				AWSOIDC: spec,
    72  			},
    73  		},
    74  	}
    75  	if err := ig.CheckAndSetDefaults(); err != nil {
    76  		return nil, trace.Wrap(err)
    77  	}
    78  	return ig, nil
    79  }
    80  
    81  // NewIntegrationAzureOIDC returns a new `azure-oidc` subkind Integration
    82  func NewIntegrationAzureOIDC(md Metadata, spec *AzureOIDCIntegrationSpecV1) (*IntegrationV1, error) {
    83  	ig := &IntegrationV1{
    84  		ResourceHeader: ResourceHeader{
    85  			Metadata: md,
    86  			Kind:     KindIntegration,
    87  			Version:  V1,
    88  			SubKind:  IntegrationSubKindAzureOIDC,
    89  		},
    90  		Spec: IntegrationSpecV1{
    91  			SubKindSpec: &IntegrationSpecV1_AzureOIDC{
    92  				AzureOIDC: spec,
    93  			},
    94  		},
    95  	}
    96  	if err := ig.CheckAndSetDefaults(); err != nil {
    97  		return nil, trace.Wrap(err)
    98  	}
    99  	return ig, nil
   100  }
   101  
   102  // String returns the integration string representation.
   103  func (ig *IntegrationV1) String() string {
   104  	return fmt.Sprintf("IntegrationV1(Name=%v, SubKind=%s, Labels=%v)",
   105  		ig.GetName(), ig.GetSubKind(), ig.GetAllLabels())
   106  }
   107  
   108  // MatchSearch goes through select field values and tries to
   109  // match against the list of search values.
   110  func (ig *IntegrationV1) MatchSearch(values []string) bool {
   111  	fieldVals := append(utils.MapToStrings(ig.GetAllLabels()), ig.GetName(), ig.GetSubKind())
   112  	return MatchSearch(fieldVals, values, nil)
   113  }
   114  
   115  // setStaticFields sets static resource header and metadata fields.
   116  func (ig *IntegrationV1) setStaticFields() {
   117  	ig.Kind = KindIntegration
   118  	ig.Version = V1
   119  }
   120  
   121  // CheckAndSetDefaults checks and sets default values
   122  func (ig *IntegrationV1) CheckAndSetDefaults() error {
   123  	ig.setStaticFields()
   124  	if err := ig.ResourceHeader.CheckAndSetDefaults(); err != nil {
   125  		return trace.Wrap(err)
   126  	}
   127  
   128  	return trace.Wrap(ig.Spec.CheckAndSetDefaults())
   129  }
   130  
   131  // CanChangeStateTo checks if the current Integration can be updated for the provided integration.
   132  func (ig *IntegrationV1) CanChangeStateTo(newState Integration) error {
   133  	if ig.SubKind != newState.GetSubKind() {
   134  		return trace.BadParameter("cannot update %q fields for a %q integration", newState.GetSubKind(), ig.SubKind)
   135  	}
   136  
   137  	if x, ok := newState.(interface{ CheckAndSetDefaults() error }); ok {
   138  		if err := x.CheckAndSetDefaults(); err != nil {
   139  			return trace.Wrap(err)
   140  		}
   141  	}
   142  
   143  	return nil
   144  }
   145  
   146  // CheckAndSetDefaults validates and sets default values for a integration.
   147  func (s *IntegrationSpecV1) CheckAndSetDefaults() error {
   148  	if s.SubKindSpec == nil {
   149  		return trace.BadParameter("missing required subkind spec")
   150  	}
   151  
   152  	switch integrationSubKind := s.SubKindSpec.(type) {
   153  	case *IntegrationSpecV1_AWSOIDC:
   154  		err := integrationSubKind.CheckAndSetDefaults()
   155  		if err != nil {
   156  			return trace.Wrap(err)
   157  		}
   158  	case *IntegrationSpecV1_AzureOIDC:
   159  		err := integrationSubKind.Validate()
   160  		if err != nil {
   161  			return trace.Wrap(err)
   162  		}
   163  	default:
   164  		return trace.BadParameter("unknown integration subkind: %T", integrationSubKind)
   165  	}
   166  
   167  	return nil
   168  }
   169  
   170  // CheckAndSetDefaults validates the configuration for AWS OIDC integration subkind.
   171  func (s *IntegrationSpecV1_AWSOIDC) CheckAndSetDefaults() error {
   172  	if s == nil || s.AWSOIDC == nil {
   173  		return trace.BadParameter("aws_oidc is required for %q subkind", IntegrationSubKindAWSOIDC)
   174  	}
   175  
   176  	if s.AWSOIDC.RoleARN == "" {
   177  		return trace.BadParameter("role_arn is required for %q subkind", IntegrationSubKindAWSOIDC)
   178  	}
   179  
   180  	// The Issuer can be empty.
   181  	// In that case it will use the cluster's web endpoint.
   182  	if s.AWSOIDC.IssuerS3URI != "" {
   183  		issuerS3URL, err := url.Parse(s.AWSOIDC.IssuerS3URI)
   184  		if err != nil {
   185  			return trace.BadParameter("unable to parse issuer s3 uri, valid format (eg, s3://my-bucket/my-prefix)")
   186  		}
   187  		if issuerS3URL.Scheme != "s3" || issuerS3URL.Host == "" || issuerS3URL.Path == "" {
   188  			return trace.BadParameter("issuer s3 uri must be in a valid format (eg, s3://my-bucket/my-prefix)")
   189  		}
   190  	}
   191  
   192  	return nil
   193  }
   194  
   195  // Validate validates the configuration for Azure OIDC integration subkind.
   196  func (s *IntegrationSpecV1_AzureOIDC) Validate() error {
   197  	if s == nil || s.AzureOIDC == nil {
   198  		return trace.BadParameter("azure_oidc is required for %q subkind", IntegrationSubKindAzureOIDC)
   199  	}
   200  	if s.AzureOIDC.TenantID == "" {
   201  		return trace.BadParameter("tenant_id must be set")
   202  	}
   203  	if s.AzureOIDC.ClientID == "" {
   204  		return trace.BadParameter("client_id must be set")
   205  	}
   206  
   207  	return nil
   208  }
   209  
   210  // GetAWSOIDCIntegrationSpec returns the specific spec fields for `aws-oidc` subkind integrations.
   211  func (ig *IntegrationV1) GetAWSOIDCIntegrationSpec() *AWSOIDCIntegrationSpecV1 {
   212  	return ig.Spec.GetAWSOIDC()
   213  }
   214  
   215  // SetAWSOIDCIntegrationSpec sets the specific fields for the `aws-oidc` subkind integration.
   216  func (ig *IntegrationV1) SetAWSOIDCIntegrationSpec(awsOIDCSpec *AWSOIDCIntegrationSpecV1) {
   217  	ig.Spec.SubKindSpec = &IntegrationSpecV1_AWSOIDC{
   218  		AWSOIDC: awsOIDCSpec,
   219  	}
   220  }
   221  
   222  // SetAWSOIDCRoleARN sets the RoleARN of the AWS OIDC Spec.
   223  func (ig *IntegrationV1) SetAWSOIDCRoleARN(roleARN string) {
   224  	currentSubSpec := ig.Spec.GetAWSOIDC()
   225  	if currentSubSpec == nil {
   226  		currentSubSpec = &AWSOIDCIntegrationSpecV1{}
   227  	}
   228  
   229  	currentSubSpec.RoleARN = roleARN
   230  	ig.Spec.SubKindSpec = &IntegrationSpecV1_AWSOIDC{
   231  		AWSOIDC: currentSubSpec,
   232  	}
   233  }
   234  
   235  // SetAWSOIDCIssuer sets the Issuer of the AWS OIDC Spec.
   236  func (ig *IntegrationV1) SetAWSOIDCIssuerS3URI(issuerS3URI string) {
   237  	currentSubSpec := ig.Spec.GetAWSOIDC()
   238  	if currentSubSpec == nil {
   239  		currentSubSpec = &AWSOIDCIntegrationSpecV1{}
   240  	}
   241  
   242  	currentSubSpec.IssuerS3URI = issuerS3URI
   243  	ig.Spec.SubKindSpec = &IntegrationSpecV1_AWSOIDC{
   244  		AWSOIDC: currentSubSpec,
   245  	}
   246  }
   247  
   248  // GetAzureOIDCIntegrationSpec returns the specific spec fields for `azure-oidc` subkind integrations.
   249  func (ig *IntegrationV1) GetAzureOIDCIntegrationSpec() *AzureOIDCIntegrationSpecV1 {
   250  	return ig.Spec.GetAzureOIDC()
   251  }
   252  
   253  // Integrations is a list of Integration resources.
   254  type Integrations []Integration
   255  
   256  // AsResources returns these groups as resources with labels.
   257  func (igs Integrations) AsResources() []ResourceWithLabels {
   258  	resources := make([]ResourceWithLabels, len(igs))
   259  	for i, ig := range igs {
   260  		resources[i] = ig
   261  	}
   262  	return resources
   263  }
   264  
   265  // Len returns the slice length.
   266  func (igs Integrations) Len() int { return len(igs) }
   267  
   268  // Less compares integrations by name.
   269  func (igs Integrations) Less(i, j int) bool { return igs[i].GetName() < igs[j].GetName() }
   270  
   271  // Swap swaps two integrations.
   272  func (igs Integrations) Swap(i, j int) { igs[i], igs[j] = igs[j], igs[i] }
   273  
   274  // UnmarshalJSON is a custom unmarshaller for JSON format.
   275  // It is required because the Spec.SubKindSpec proto field is a oneof.
   276  // This translates into two issues when generating golang code:
   277  // - the Spec.SubKindSpec field in Go is an interface
   278  // - it creates an extra field to store the oneof values
   279  //
   280  // Spec.SubKindSpec is an interface because it can have one of multiple values,
   281  // even though there's only one type for now: aws_oidc.
   282  // When trying to unmarshal this field, we must provide a concrete type.
   283  // To do so, we unmarshal just the root fields (ResourceHeader: Name, Kind, SubKind, Version, Metadata)
   284  // and then use its SubKind to provide a concrete type for the Spec.SubKindSpec field.
   285  // Unmarshalling the remaining fields uses the standard json.Unmarshal over the Spec field.
   286  //
   287  // Spec.SubKindSpec is an extra field which only adds clutter
   288  // This method pulls those fields into a higher level.
   289  // So, instead of:
   290  //
   291  // spec.subkind_spec.aws_oidc.role_arn: xyz
   292  //
   293  // It will be:
   294  //
   295  // spec.aws_oidc.role_arn: xyz
   296  func (ig *IntegrationV1) UnmarshalJSON(data []byte) error {
   297  	var integration IntegrationV1
   298  
   299  	d := struct {
   300  		ResourceHeader `json:""`
   301  		Spec           struct {
   302  			AWSOIDC   json.RawMessage `json:"aws_oidc"`
   303  			AzureOIDC json.RawMessage `json:"azure_oidc"`
   304  		} `json:"spec"`
   305  	}{}
   306  
   307  	err := json.Unmarshal(data, &d)
   308  	if err != nil {
   309  		return trace.Wrap(err)
   310  	}
   311  
   312  	integration.ResourceHeader = d.ResourceHeader
   313  
   314  	switch integration.SubKind {
   315  	case IntegrationSubKindAWSOIDC:
   316  		subkindSpec := &IntegrationSpecV1_AWSOIDC{
   317  			AWSOIDC: &AWSOIDCIntegrationSpecV1{},
   318  		}
   319  
   320  		if err := json.Unmarshal(d.Spec.AWSOIDC, subkindSpec.AWSOIDC); err != nil {
   321  			return trace.Wrap(err)
   322  		}
   323  
   324  		integration.Spec.SubKindSpec = subkindSpec
   325  
   326  	case IntegrationSubKindAzureOIDC:
   327  		subkindSpec := &IntegrationSpecV1_AzureOIDC{
   328  			AzureOIDC: &AzureOIDCIntegrationSpecV1{},
   329  		}
   330  
   331  		if err := json.Unmarshal(d.Spec.AzureOIDC, subkindSpec.AzureOIDC); err != nil {
   332  			return trace.Wrap(err)
   333  		}
   334  
   335  		integration.Spec.SubKindSpec = subkindSpec
   336  
   337  	default:
   338  		return trace.BadParameter("invalid subkind %q", integration.ResourceHeader.SubKind)
   339  	}
   340  
   341  	if err := integration.CheckAndSetDefaults(); err != nil {
   342  		return trace.Wrap(err)
   343  	}
   344  
   345  	*ig = integration
   346  	return nil
   347  }
   348  
   349  // MarshalJSON is a custom marshaller for JSON format.
   350  // gogoproto doesn't allow for oneof json tags [https://github.com/gogo/protobuf/issues/623]
   351  // So, this is required to correctly use snake_case for every field.
   352  // Please see [IntegrationV1.UnmarshalJSON] for more information.
   353  func (ig *IntegrationV1) MarshalJSON() ([]byte, error) {
   354  	d := struct {
   355  		ResourceHeader `json:""`
   356  		Spec           struct {
   357  			AWSOIDC   AWSOIDCIntegrationSpecV1   `json:"aws_oidc,omitempty"`
   358  			AzureOIDC AzureOIDCIntegrationSpecV1 `json:"azure_oidc,omitempty"`
   359  		} `json:"spec"`
   360  	}{}
   361  
   362  	d.ResourceHeader = ig.ResourceHeader
   363  
   364  	switch ig.SubKind {
   365  	case IntegrationSubKindAWSOIDC:
   366  		if ig.GetAWSOIDCIntegrationSpec() == nil {
   367  			return nil, trace.BadParameter("missing subkind data for %q subkind", ig.SubKind)
   368  		}
   369  
   370  		d.Spec.AWSOIDC = *ig.GetAWSOIDCIntegrationSpec()
   371  	case IntegrationSubKindAzureOIDC:
   372  		if ig.GetAzureOIDCIntegrationSpec() == nil {
   373  			return nil, trace.BadParameter("missing subkind data for %q subkind", ig.SubKind)
   374  		}
   375  
   376  		d.Spec.AzureOIDC = *ig.GetAzureOIDCIntegrationSpec()
   377  	default:
   378  		return nil, trace.BadParameter("invalid subkind %q", ig.SubKind)
   379  	}
   380  
   381  	out, err := json.Marshal(d)
   382  	return out, trace.Wrap(err)
   383  }