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  }