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 }