Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v14] Add support for include_enterprise_slug for the github join method (#35860) #35900

Merged
merged 1 commit into from Dec 20, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions api/proto/teleport/legacy/types/types.proto
Expand Up @@ -1292,6 +1292,17 @@ message ProvisionTokenSpecV2GitHub {
// include the scheme or a path. The instance must be accessible over HTTPS
// at this hostname and the certificate must be trusted by the Auth Server.
string EnterpriseServerHost = 2 [(gogoproto.jsontag) = "enterprise_server_host,omitempty"];
// EnterpriseSlug allows the slug of a GitHub Enterprise organisation to be
// included in the expected issuer of the OIDC tokens. This is for
// compatibility with the `include_enterprise_slug` option in GHE.
//
// This field should be set to the slug of your enterprise if this is enabled. If
// this is not enabled, then this field must be left empty. This field cannot
// be specified if `enterprise_server_host` is specified.
//
// See https://docs.github.com/en/enterprise-cloud@latest/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-issuer-value-for-an-enterprise
// for more information about customised issuer values.
string EnterpriseSlug = 3 [(gogoproto.jsontag) = "enterprise_slug,omitempty"];
}

// ProvisionTokenSpecV2GitLab contains the GitLab-specific part of the
Expand Down
3 changes: 3 additions & 0 deletions api/types/provisioning.go
Expand Up @@ -587,6 +587,9 @@ func (a *ProvisionTokenSpecV2GitHub) checkAndSetDefaults() error {
if strings.Contains(a.EnterpriseServerHost, "/") {
return trace.BadParameter("'spec.github.enterprise_server_host' should not contain the scheme or path")
}
if a.EnterpriseServerHost != "" && a.EnterpriseSlug != "" {
return trace.BadParameter("'spec.github.enterprise_server_host' and `spec.github.enterprise_slug` cannot both be set")
}
return nil
}

Expand Down
22 changes: 22 additions & 0 deletions api/types/provisioning_test.go
Expand Up @@ -318,6 +318,28 @@ func TestProvisionTokenV2_CheckAndSetDefaults(t *testing.T) {
},
expectedErr: &trace.BadParameterError{},
},
{
desc: "github slug and ghes set",
token: &ProvisionTokenV2{
Metadata: Metadata{
Name: "test",
},
Spec: ProvisionTokenSpecV2{
Roles: []SystemRole{RoleNode},
JoinMethod: JoinMethodGitHub,
GitHub: &ProvisionTokenSpecV2GitHub{
EnterpriseServerHost: "example.com",
EnterpriseSlug: "slug",
Allow: []*ProvisionTokenSpecV2GitHub_Rule{
{
Sub: "foo",
},
},
},
},
},
expectedErr: &trace.BadParameterError{},
},
{
desc: "circleci valid",
token: &ProvisionTokenV2{
Expand Down
3,045 changes: 1,550 additions & 1,495 deletions api/types/types.pb.go

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions docs/pages/includes/provision-token/github-spec.mdx
Expand Up @@ -22,6 +22,18 @@ spec:
# this value should be configured to the hostname of your GHES instance.
enterprise_server_host: ghes.example.com

# enterprise_slug allows the slug of a GitHub Enterprise organisation to be
# included in the expected issuer of the OIDC tokens. This is for
# compatibility with the include_enterprise_slug option in GHE.
#
# This field should be set to the slug of your Github Enterprise organization if this is enabled. If
# this is not enabled, then this field must be left empty. This field cannot
# be specified if `enterprise_server_host` is specified.
#
# See https://docs.github.com/en/enterprise-cloud@latest/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-issuer-value-for-an-enterprise
# for more information about customized issuer values.
enterprise_slug: slug

# allow is an array of rule configurations for what GitHub Actions workflows
# should be allowed to join. All options configured within one allow entry
# must be satisfied for the GitHub Actions run to be allowed to join. Where
Expand Down
20 changes: 19 additions & 1 deletion docs/pages/machine-id/deployment/github-actions.mdx
Expand Up @@ -61,7 +61,9 @@ Replace `gravitational/example` with the name of the repository that `tbot`
will run within. You may also choose to change the name of the bot and token
to more accurately describe your use-case.

<Admonition type="note" title="Using GitHub Enterprise Server?">
<Admonition type="note" title="Using GitHub Enterprise?">
**Enterprise Server**

From Teleport 11.1.4, users with Teleport Enterprise are able to permit
workflows within GitHub Enterprise Server instances to authenticate using the
GitHub join method.
Expand All @@ -78,6 +80,22 @@ spec:
github:
enterprise_server_host: ghes.example.com
```

**Enterprise Cloud**

If you have enabled `include_enterprise_slug` in your GitHub Enterprise
Cloud configuration, you will need to set `spec.github.enterprise_slug` to
the slug of your GitHub Enterprise organization.

For example:
```yaml
spec:
github:
enterprise_slug: my-enterprise
```

Read more about `include_enterprise_slug` on the the GitHub guide to
[customizing the issuer value for an enterprise](https://docs.github.com/en/enterprise-cloud@latest/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-issuer-value-for-an-enterprise).
</Admonition>

Once the resource file has been written, create the token with `tctl`:
Expand Down
Expand Up @@ -181,6 +181,16 @@ spec:
must be accessible over HTTPS at this hostname and the certificate
must be trusted by the Auth Server.
type: string
enterprise_slug:
description: EnterpriseSlug allows the slug of a GitHub Enterprise
organisation to be included in the expected issuer of the OIDC
tokens. This is for compatibility with the `include_enterprise_slug`
option in GHE. This field should be set to the slug of your
enterprise if this is enabled. If this is not enabled, then
this field must be left empty. This field cannot be specified
if `enterprise_server_host` is specified. See https://docs.github.com/en/enterprise-cloud@latest/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-issuer-value-for-an-enterprise
for more information about customised issuer values.
type: string
type: object
gitlab:
description: GitLab allows the configuration of options specific to
Expand Down
Expand Up @@ -181,6 +181,16 @@ spec:
must be accessible over HTTPS at this hostname and the certificate
must be trusted by the Auth Server.
type: string
enterprise_slug:
description: EnterpriseSlug allows the slug of a GitHub Enterprise
organisation to be included in the expected issuer of the OIDC
tokens. This is for compatibility with the `include_enterprise_slug`
option in GHE. This field should be set to the slug of your
enterprise if this is enabled. If this is not enabled, then
this field must be left empty. This field cannot be specified
if `enterprise_server_host` is specified. See https://docs.github.com/en/enterprise-cloud@latest/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-issuer-value-for-an-enterprise
for more information about customised issuer values.
type: string
type: object
gitlab:
description: GitLab allows the configuration of options specific to
Expand Down
7 changes: 4 additions & 3 deletions lib/auth/join_github.go
Expand Up @@ -30,7 +30,7 @@ import (

type ghaIDTokenValidator interface {
Validate(
ctx context.Context, GHESHost string, token string,
ctx context.Context, GHESHost string, enterpriseSlug string, token string,
) (*githubactions.IDTokenClaims, error)
}

Expand All @@ -51,7 +51,8 @@ func (a *Server) checkGitHubJoinRequest(ctx context.Context, req *types.Register
// enterpriseOverride is a hostname to use instead of github.com when
// validating tokens. This allows GHES instances to be connected.
enterpriseOverride := token.Spec.GitHub.EnterpriseServerHost
if enterpriseOverride != "" {
enterpriseSlug := token.Spec.GitHub.EnterpriseSlug
if enterpriseOverride != "" || enterpriseSlug != "" {
if modules.GetModules().BuildType() != modules.BuildEnterprise {
return nil, fmt.Errorf(
"github enterprise server joining: %w",
Expand All @@ -61,7 +62,7 @@ func (a *Server) checkGitHubJoinRequest(ctx context.Context, req *types.Register
}

claims, err := a.ghaIDTokenValidator.Validate(
ctx, enterpriseOverride, req.IDToken,
ctx, enterpriseOverride, enterpriseSlug, req.IDToken,
)
if err != nil {
return nil, trace.Wrap(err)
Expand Down
70 changes: 59 additions & 11 deletions lib/auth/join_github_test.go
Expand Up @@ -32,15 +32,18 @@ import (
)

type mockIDTokenValidator struct {
tokens map[string]githubactions.IDTokenClaims
lastCalledGHESHost string
tokens map[string]githubactions.IDTokenClaims
lastCalledGHESHost string
lastCalledEnterpriseSlug string
}

var errMockInvalidToken = errors.New("invalid token")

func (m *mockIDTokenValidator) Validate(_ context.Context, ghes string, token string) (*githubactions.IDTokenClaims, error) {
func (m *mockIDTokenValidator) Validate(
_ context.Context, ghes, enterpriseSlug, token string,
) (*githubactions.IDTokenClaims, error) {
m.lastCalledGHESHost = ghes

m.lastCalledEnterpriseSlug = enterpriseSlug
claims, ok := m.tokens[token]
if !ok {
return nil, errMockInvalidToken
Expand All @@ -49,6 +52,11 @@ func (m *mockIDTokenValidator) Validate(_ context.Context, ghes string, token st
return &claims, nil
}

func (m *mockIDTokenValidator) reset() {
m.lastCalledGHESHost = ""
m.lastCalledEnterpriseSlug = ""
}

func TestAuth_RegisterUsingToken_GHA(t *testing.T) {
validIDToken := "test.fake.jwt"
idTokenValidator := &mockIDTokenValidator{
Expand Down Expand Up @@ -147,6 +155,22 @@ func TestAuth_RegisterUsingToken_GHA(t *testing.T) {
assertError: require.NoError,
setEnterprise: true,
},
{
name: "enterprise slug",
tokenSpec: types.ProvisionTokenSpecV2{
JoinMethod: types.JoinMethodGitHub,
Roles: []types.SystemRole{types.RoleNode},
GitHub: &types.ProvisionTokenSpecV2GitHub{
EnterpriseSlug: "slug",
Allow: []*types.ProvisionTokenSpecV2GitHub_Rule{
allowRule(nil),
},
},
},
setEnterprise: true,
request: newRequest(validIDToken),
assertError: require.NoError,
},
{
name: "ghes override requires enterprise license",
tokenSpec: types.ProvisionTokenSpecV2{
Expand All @@ -164,6 +188,23 @@ func TestAuth_RegisterUsingToken_GHA(t *testing.T) {
require.ErrorIs(t, err, ErrRequiresEnterprise)
}),
},
{
name: "enterprise slug requires enterprise license",
tokenSpec: types.ProvisionTokenSpecV2{
JoinMethod: types.JoinMethodGitHub,
Roles: []types.SystemRole{types.RoleNode},
GitHub: &types.ProvisionTokenSpecV2GitHub{
EnterpriseSlug: "slug",
Allow: []*types.ProvisionTokenSpecV2GitHub_Rule{
allowRule(nil),
},
},
},
request: newRequest(validIDToken),
assertError: require.ErrorAssertionFunc(func(t require.TestingT, err error, i ...interface{}) {
require.ErrorIs(t, err, ErrRequiresEnterprise)
}),
},
{
name: "multiple allow rules",
tokenSpec: types.ProvisionTokenSpecV2{
Expand Down Expand Up @@ -312,6 +353,7 @@ func TestAuth_RegisterUsingToken_GHA(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Cleanup(idTokenValidator.reset)
if tt.setEnterprise {
modules.SetTestModules(
t,
Expand All @@ -327,14 +369,20 @@ func TestAuth_RegisterUsingToken_GHA(t *testing.T) {

_, err = auth.RegisterUsingToken(ctx, tt.request)
tt.assertError(t, err)

if tt.tokenSpec.GitHub.EnterpriseServerHost != "" {
require.Equal(
t,
tt.tokenSpec.GitHub.EnterpriseServerHost,
idTokenValidator.lastCalledGHESHost,
)
if err != nil {
return
}

require.Equal(
t,
tt.tokenSpec.GitHub.EnterpriseServerHost,
idTokenValidator.lastCalledGHESHost,
)
require.Equal(
t,
tt.tokenSpec.GitHub.EnterpriseSlug,
idTokenValidator.lastCalledEnterpriseSlug,
)
})
}
}
18 changes: 14 additions & 4 deletions lib/githubactions/token_validator.go
Expand Up @@ -58,22 +58,32 @@ func NewIDTokenValidator(cfg IDTokenValidatorConfig) *IDTokenValidator {
}
}

func (id *IDTokenValidator) issuerURL(GHESHost string) string {
func (id *IDTokenValidator) issuerURL(
GHESHost string, enterpriseSlug string,
) string {
scheme := "https"
if id.insecure {
scheme = "http"
}

if GHESHost == "" {
return fmt.Sprintf("%s://%s", scheme, id.GitHubIssuerHost)
url := fmt.Sprintf("%s://%s", scheme, id.GitHubIssuerHost)
// Support custom enterprise slugs, as per:
// https://docs.github.com/en/enterprise-cloud@latest/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-issuer-value-for-an-enterprise
if enterpriseSlug != "" {
url = fmt.Sprintf("%s/%s", url, enterpriseSlug)
}
return url
}
return fmt.Sprintf("%s://%s/_services/token", scheme, GHESHost)
}

func (id *IDTokenValidator) Validate(ctx context.Context, GHESHost string, token string) (*IDTokenClaims, error) {
func (id *IDTokenValidator) Validate(
ctx context.Context, GHESHost string, enterpriseSlug string, token string,
) (*IDTokenClaims, error) {
p, err := oidc.NewProvider(
ctx,
id.issuerURL(GHESHost),
id.issuerURL(GHESHost, enterpriseSlug),
)
if err != nil {
return nil, trace.Wrap(err)
Expand Down