github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/types/github.go (about) 1 /* 2 Copyright 2020 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 "context" 21 "log/slog" 22 "time" 23 24 "github.com/gravitational/trace" 25 "golang.org/x/crypto/ssh" 26 27 "github.com/gravitational/teleport/api/defaults" 28 "github.com/gravitational/teleport/api/utils" 29 ) 30 31 const ( 32 GithubURL = "https://github.com" 33 GithubAPIURL = "https://api.github.com" 34 ) 35 36 // GithubConnector defines an interface for a Github OAuth2 connector 37 type GithubConnector interface { 38 // ResourceWithSecrets is a common interface for all resources 39 ResourceWithSecrets 40 ResourceWithOrigin 41 // SetMetadata sets object metadata 42 SetMetadata(meta Metadata) 43 // GetClientID returns the connector client ID 44 GetClientID() string 45 // SetClientID sets the connector client ID 46 SetClientID(string) 47 // GetClientSecret returns the connector client secret 48 GetClientSecret() string 49 // SetClientSecret sets the connector client secret 50 SetClientSecret(string) 51 // GetRedirectURL returns the connector redirect URL 52 GetRedirectURL() string 53 // SetRedirectURL sets the connector redirect URL 54 SetRedirectURL(string) 55 // GetTeamsToLogins returns the mapping of Github teams to allowed logins 56 GetTeamsToLogins() []TeamMapping 57 // SetTeamsToLogins sets the mapping of Github teams to allowed logins 58 SetTeamsToLogins([]TeamMapping) 59 // GetTeamsToRoles returns the mapping of Github teams to allowed roles 60 GetTeamsToRoles() []TeamRolesMapping 61 // SetTeamsToRoles sets the mapping of Github teams to allowed roles 62 SetTeamsToRoles([]TeamRolesMapping) 63 // MapClaims returns the list of allows logins based on the retrieved claims 64 // returns list of logins and kubernetes groups 65 MapClaims(GithubClaims) (roles []string, kubeGroups []string, kubeUsers []string) 66 // GetDisplay returns the connector display name 67 GetDisplay() string 68 // SetDisplay sets the connector display name 69 SetDisplay(string) 70 // GetEndpointURL returns the endpoint URL 71 GetEndpointURL() string 72 // GetAPIEndpointURL returns the API endpoint URL 73 GetAPIEndpointURL() string 74 } 75 76 // NewGithubConnector creates a new Github connector from name and spec 77 func NewGithubConnector(name string, spec GithubConnectorSpecV3) (GithubConnector, error) { 78 c := &GithubConnectorV3{ 79 Metadata: Metadata{ 80 Name: name, 81 }, 82 Spec: spec, 83 } 84 if err := c.CheckAndSetDefaults(); err != nil { 85 return nil, trace.Wrap(err) 86 } 87 return c, nil 88 } 89 90 // GetVersion returns resource version 91 func (c *GithubConnectorV3) GetVersion() string { 92 return c.Version 93 } 94 95 // GetKind returns resource kind 96 func (c *GithubConnectorV3) GetKind() string { 97 return c.Kind 98 } 99 100 // GetSubKind returns resource sub kind 101 func (c *GithubConnectorV3) GetSubKind() string { 102 return c.SubKind 103 } 104 105 // SetSubKind sets resource subkind 106 func (c *GithubConnectorV3) SetSubKind(s string) { 107 c.SubKind = s 108 } 109 110 // GetResourceID returns resource ID 111 func (c *GithubConnectorV3) GetResourceID() int64 { 112 return c.Metadata.ID 113 } 114 115 // SetResourceID sets resource ID 116 func (c *GithubConnectorV3) SetResourceID(id int64) { 117 c.Metadata.ID = id 118 } 119 120 // GetRevision returns the revision 121 func (c *GithubConnectorV3) GetRevision() string { 122 return c.Metadata.GetRevision() 123 } 124 125 // SetRevision sets the revision 126 func (c *GithubConnectorV3) SetRevision(rev string) { 127 c.Metadata.SetRevision(rev) 128 } 129 130 // GetName returns the name of the connector 131 func (c *GithubConnectorV3) GetName() string { 132 return c.Metadata.GetName() 133 } 134 135 // SetName sets the connector name 136 func (c *GithubConnectorV3) SetName(name string) { 137 c.Metadata.SetName(name) 138 } 139 140 // Expiry returns the connector expiration time 141 func (c *GithubConnectorV3) Expiry() time.Time { 142 return c.Metadata.Expiry() 143 } 144 145 // SetExpiry sets the connector expiration time 146 func (c *GithubConnectorV3) SetExpiry(expires time.Time) { 147 c.Metadata.SetExpiry(expires) 148 } 149 150 // SetMetadata sets connector metadata 151 func (c *GithubConnectorV3) SetMetadata(meta Metadata) { 152 c.Metadata = meta 153 } 154 155 // GetMetadata returns the connector metadata 156 func (c *GithubConnectorV3) GetMetadata() Metadata { 157 return c.Metadata 158 } 159 160 // Origin returns the origin value of the resource. 161 func (c *GithubConnectorV3) Origin() string { 162 return c.Metadata.Origin() 163 } 164 165 // SetOrigin sets the origin value of the resource. 166 func (c *GithubConnectorV3) SetOrigin(origin string) { 167 c.Metadata.SetOrigin(origin) 168 } 169 170 // WithoutSecrets returns an instance of resource without secrets. 171 func (c *GithubConnectorV3) WithoutSecrets() Resource { 172 if c.GetClientSecret() == "" { 173 return c 174 } 175 c2 := *c 176 c2.SetClientSecret("") 177 return &c2 178 } 179 180 // setStaticFields sets static resource header and metadata fields. 181 func (c *GithubConnectorV3) setStaticFields() { 182 c.Kind = KindGithubConnector 183 c.Version = V3 184 } 185 186 // CheckAndSetDefaults verifies the connector is valid and sets some defaults 187 func (c *GithubConnectorV3) CheckAndSetDefaults() error { 188 c.setStaticFields() 189 if err := c.Metadata.CheckAndSetDefaults(); err != nil { 190 return trace.Wrap(err) 191 } 192 193 // DELETE IN 11.0.0 194 if len(c.Spec.TeamsToLogins) > 0 { 195 slog.WarnContext(context.Background(), "GitHub connector field teams_to_logins is deprecated and will be removed in the next version. Please use teams_to_roles instead.") 196 } 197 198 // make sure claim mappings have either roles or a role template 199 for i, v := range c.Spec.TeamsToLogins { 200 if v.Team == "" { 201 return trace.BadParameter("team_to_logins mapping #%v is invalid, team is empty.", i+1) 202 } 203 } 204 for i, v := range c.Spec.TeamsToRoles { 205 if v.Team == "" { 206 return trace.BadParameter("team_to_roles mapping #%v is invalid, team is empty.", i+1) 207 } 208 } 209 210 if len(c.Spec.TeamsToLogins)+len(c.Spec.TeamsToRoles) == 0 { 211 return trace.BadParameter("team_to_logins or team_to_roles mapping is invalid, no mappings defined.") 212 } 213 214 return nil 215 } 216 217 // GetClientID returns the connector client ID 218 func (c *GithubConnectorV3) GetClientID() string { 219 return c.Spec.ClientID 220 } 221 222 // SetClientID sets the connector client ID 223 func (c *GithubConnectorV3) SetClientID(id string) { 224 c.Spec.ClientID = id 225 } 226 227 // GetClientSecret returns the connector client secret 228 func (c *GithubConnectorV3) GetClientSecret() string { 229 return c.Spec.ClientSecret 230 } 231 232 // SetClientSecret sets the connector client secret 233 func (c *GithubConnectorV3) SetClientSecret(secret string) { 234 c.Spec.ClientSecret = secret 235 } 236 237 // GetRedirectURL returns the connector redirect URL 238 func (c *GithubConnectorV3) GetRedirectURL() string { 239 return c.Spec.RedirectURL 240 } 241 242 // SetRedirectURL sets the connector redirect URL 243 func (c *GithubConnectorV3) SetRedirectURL(redirectURL string) { 244 c.Spec.RedirectURL = redirectURL 245 } 246 247 // GetTeamsToLogins returns the connector team membership mappings 248 // 249 // DEPRECATED: use GetTeamsToRoles instead 250 func (c *GithubConnectorV3) GetTeamsToLogins() []TeamMapping { 251 return c.Spec.TeamsToLogins 252 } 253 254 // SetTeamsToLogins sets the connector team membership mappings 255 // 256 // DEPRECATED: use SetTeamsToRoles instead 257 func (c *GithubConnectorV3) SetTeamsToLogins(teamsToLogins []TeamMapping) { 258 c.Spec.TeamsToLogins = teamsToLogins 259 } 260 261 // GetTeamsToRoles returns the mapping of Github teams to allowed roles 262 func (c *GithubConnectorV3) GetTeamsToRoles() []TeamRolesMapping { 263 return c.Spec.TeamsToRoles 264 } 265 266 // SetTeamsToRoles sets the mapping of Github teams to allowed roles 267 func (c *GithubConnectorV3) SetTeamsToRoles(m []TeamRolesMapping) { 268 c.Spec.TeamsToRoles = m 269 } 270 271 // GetDisplay returns the connector display name 272 func (c *GithubConnectorV3) GetDisplay() string { 273 return c.Spec.Display 274 } 275 276 // SetDisplay sets the connector display name 277 func (c *GithubConnectorV3) SetDisplay(display string) { 278 c.Spec.Display = display 279 } 280 281 // GetEndpointURL returns the endpoint URL 282 func (c *GithubConnectorV3) GetEndpointURL() string { 283 return GithubURL 284 } 285 286 // GetEndpointURL returns the API endpoint URL 287 func (c *GithubConnectorV3) GetAPIEndpointURL() string { 288 return GithubAPIURL 289 } 290 291 // MapClaims returns a list of logins based on the provided claims, 292 // returns a list of logins and list of kubernetes groups 293 func (c *GithubConnectorV3) MapClaims(claims GithubClaims) ([]string, []string, []string) { 294 var roles, kubeGroups, kubeUsers []string 295 for _, mapping := range c.GetTeamsToLogins() { 296 teams, ok := claims.OrganizationToTeams[mapping.Organization] 297 if !ok { 298 // the user does not belong to this organization 299 continue 300 } 301 for _, team := range teams { 302 // see if the user belongs to this team 303 if team == mapping.Team { 304 roles = append(roles, mapping.Logins...) 305 kubeGroups = append(kubeGroups, mapping.KubeGroups...) 306 kubeUsers = append(kubeUsers, mapping.KubeUsers...) 307 } 308 } 309 } 310 for _, mapping := range c.GetTeamsToRoles() { 311 teams, ok := claims.OrganizationToTeams[mapping.Organization] 312 if !ok { 313 // the user does not belong to this organization 314 continue 315 } 316 for _, team := range teams { 317 // see if the user belongs to this team 318 if team == mapping.Team { 319 roles = append(roles, mapping.Roles...) 320 } 321 } 322 } 323 return utils.Deduplicate(roles), utils.Deduplicate(kubeGroups), utils.Deduplicate(kubeUsers) 324 } 325 326 // SetExpiry sets expiry time for the object 327 func (r *GithubAuthRequest) SetExpiry(expires time.Time) { 328 r.Expires = &expires 329 } 330 331 // Expiry returns object expiry setting. 332 func (r *GithubAuthRequest) Expiry() time.Time { 333 if r.Expires == nil { 334 return time.Time{} 335 } 336 return *r.Expires 337 } 338 339 // Check makes sure the request is valid 340 func (r *GithubAuthRequest) Check() error { 341 if r.ConnectorID == "" { 342 return trace.BadParameter("missing ConnectorID") 343 } 344 if r.StateToken == "" { 345 return trace.BadParameter("missing StateToken") 346 } 347 if len(r.PublicKey) != 0 { 348 _, _, _, _, err := ssh.ParseAuthorizedKey(r.PublicKey) 349 if err != nil { 350 return trace.BadParameter("bad PublicKey: %v", err) 351 } 352 if (r.CertTTL > defaults.MaxCertDuration) || (r.CertTTL < defaults.MinCertDuration) { 353 return trace.BadParameter("wrong CertTTL") 354 } 355 } 356 357 // we could collapse these two checks into one, but the error message would become ambiguous. 358 if r.SSOTestFlow && r.ConnectorSpec == nil { 359 return trace.BadParameter("ConnectorSpec cannot be nil when SSOTestFlow is true") 360 } 361 362 if !r.SSOTestFlow && r.ConnectorSpec != nil { 363 return trace.BadParameter("ConnectorSpec must be nil when SSOTestFlow is false") 364 } 365 366 return nil 367 }