Skip to content

Commit

Permalink
[v16] Machine ID: Add bitbucket join method for Bitbucket Pipelines…
Browse files Browse the repository at this point in the history
… joining (#49337)

* Machine ID: Add `bitbucket` join method for Bitbucket Pipelines joining (#48724)

* Add `bitbucket` join method for Bitbucket Pipelines joining

This adds a new `bitbucket` join method that Machine ID bots can use
to authenticate to Teleport from Bitbucket Pipelines CI runs.

* Add unit tests for bitbucket joining

This also fully adds `deployment_environment_uuid` which was found to
be missing.

* Fix imports

* Update tf docs

* Docs update

* Update generated TF resources

* Attempt to work around docs linter

* Add provision token tests

* Remove pipeline_uuid and step_uuid from protos

Also, fix deploment_environment_uuid field name.

* Remove references to removed fields in tests

* Switch to go-oidc/v3 and remove now-redundant nbf check

* Fix go.mod imports for the TF provider

* Fix event-handler go.mod

* Address review feedback; add 15s timeout to fetch provider metadata

* Update lib/bitbucket/token_validator.go

Co-authored-by: rosstimothy <[email protected]>

* Fix build after constant rename

---------

Co-authored-by: rosstimothy <[email protected]>

* Fix logger for v16

---------

Co-authored-by: rosstimothy <[email protected]>
  • Loading branch information
timothyb89 and rosstimothy authored Nov 27, 2024
1 parent 2ed08c7 commit 5bfc4d8
Show file tree
Hide file tree
Showing 27 changed files with 4,792 additions and 2,446 deletions.
45 changes: 45 additions & 0 deletions api/proto/teleport/legacy/types/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1317,6 +1317,8 @@ message ProvisionTokenSpecV2 {
ProvisionTokenSpecV2TPM TPM = 15 [(gogoproto.jsontag) = "tpm,omitempty"];
// TerraformCloud allows the configuration of options specific to the "terraform_cloud" join method.
ProvisionTokenSpecV2TerraformCloud TerraformCloud = 16 [(gogoproto.jsontag) = "terraform_cloud,omitempty"];
// Bitbucket allows the configuration of options specific to the "bitbucket" join method.
ProvisionTokenSpecV2Bitbucket Bitbucket = 17 [(gogoproto.jsontag) = "bitbucket,omitempty"];
}

// ProvisionTokenSpecV2TPM contains the TPM-specific part of the
Expand Down Expand Up @@ -1662,6 +1664,49 @@ message ProvisionTokenSpecV2TerraformCloud {
string Hostname = 3 [(gogoproto.jsontag) = "hostname,omitempty"];
}

message ProvisionTokenSpecV2Bitbucket {
// Rule is a set of properties the Bitbucket-issued token might have to be
// allowed to use this ProvisionToken.
message Rule {
// WorkspaceUUID is the UUID of the workspace for which this token was
// issued. Bitbucket UUIDs must begin and end with braces, e.g. `{...}`.
// This value may be found in the Pipelines -> OpenID Connect section of the
// repository settings.
string WorkspaceUUID = 1 [(gogoproto.jsontag) = "workspace_uuid,omitempty"];

// RepositoryUUID is the UUID of the repository for which this token was
// issued. Bitbucket UUIDs must begin and end with braces, e.g. `{...}`.
// This value may be found in the Pipelines -> OpenID Connect section of the
// repository settings.
string RepositoryUUID = 2 [(gogoproto.jsontag) = "repository_uuid,omitempty"];

// DeploymentEnvironmentUUID is the UUID of the deployment environment
// targeted by this pipelines run, if any. These values may be found in the
// "Pipelines -> OpenID Connect -> Deployment environments" section of the
// repository settings.
string DeploymentEnvironmentUUID = 3 [(gogoproto.jsontag) = "deployment_environment_uuid,omitempty"];

// BranchName is the name of the branch on which this pipeline executed.
string BranchName = 4 [(gogoproto.jsontag) = "branch_name,omitempty"];
}

// Allow is a list of Rules, nodes using this token must match one
// allow rule to use this token.
repeated Rule Allow = 1 [(gogoproto.jsontag) = "allow,omitempty"];

// Audience is a Bitbucket-specified audience value for this token. It is
// unique to each Bitbucket repository, and must be set to the value as
// written in the Pipelines -> OpenID Connect section of the repository
// settings.
string Audience = 2 [(gogoproto.jsontag) = "audience,omitempty"];

// IdentityProviderURL is a Bitbucket-specified issuer URL for incoming OIDC
// tokens. It is unique to each Bitbucket repository, and must be set to the
// value as written in the Pipelines -> OpenID Connect section of the
// repository settings.
string IdentityProviderURL = 3 [(gogoproto.jsontag) = "identity_provider_url,omitempty"];
}

// StaticTokensV2 implements the StaticTokens interface.
message StaticTokensV2 {
option (gogoproto.goproto_stringer) = false;
Expand Down
42 changes: 42 additions & 0 deletions api/types/provisioning.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ const (
// JoinMethodTerraformCloud indicates that the node will join using the Terraform
// join method. See lib/terraformcloud for more.
JoinMethodTerraformCloud JoinMethod = "terraform_cloud"
// JoinMethodBitbucket indicates that the node will join using the Bitbucket
// join method. See lib/bitbucket for more.
JoinMethodBitbucket JoinMethod = "bitbucket"
)

var JoinMethods = []JoinMethod{
Expand Down Expand Up @@ -363,6 +366,17 @@ func (p *ProvisionTokenV2) CheckAndSetDefaults() error {
if err := providerCfg.checkAndSetDefaults(); err != nil {
return trace.Wrap(err, "spec.terraform_cloud: failed validation")
}
case JoinMethodBitbucket:
providerCfg := p.Spec.Bitbucket
if providerCfg == nil {
return trace.BadParameter(
"spec.bitbucket: must be configured for the join method %q",
JoinMethodBitbucket,
)
}
if err := providerCfg.checkAndSetDefaults(); err != nil {
return trace.Wrap(err, "spec.bitbucket: failed validation")
}
default:
return trace.BadParameter("unknown join method %q", p.Spec.JoinMethod)
}
Expand Down Expand Up @@ -862,3 +876,31 @@ func (a *ProvisionTokenSpecV2TerraformCloud) checkAndSetDefaults() error {

return nil
}

func (a *ProvisionTokenSpecV2Bitbucket) checkAndSetDefaults() error {
if len(a.Allow) == 0 {
return trace.BadParameter("the %q join method requires at least one token allow rule", JoinMethodBitbucket)
}

if a.Audience == "" {
return trace.BadParameter("audience: an OpenID Connect Audience value is required")
}

if a.IdentityProviderURL == "" {
return trace.BadParameter("identity_provider_url: an identity provider URL is required")
}

for i, rule := range a.Allow {
workspaceSet := rule.WorkspaceUUID != ""
repositorySet := rule.RepositoryUUID != ""

if !workspaceSet && !repositorySet {
return trace.BadParameter(
"allow[%d]: at least one of ['workspace_uuid', 'repository_uuid'] must be set",
i,
)
}
}

return nil
}
108 changes: 108 additions & 0 deletions api/types/provisioning_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,114 @@ func TestProvisionTokenV2_CheckAndSetDefaults(t *testing.T) {
},
wantErr: true,
},
{
desc: "bitbucket only workspace",
token: &ProvisionTokenV2{
Metadata: Metadata{
Name: "test",
},
Spec: ProvisionTokenSpecV2{
Roles: []SystemRole{RoleNode},
JoinMethod: JoinMethodBitbucket,
Bitbucket: &ProvisionTokenSpecV2Bitbucket{
Audience: "foo",
IdentityProviderURL: "https://example.com",
Allow: []*ProvisionTokenSpecV2Bitbucket_Rule{
{
WorkspaceUUID: "{foo}",
},
},
},
},
},
wantErr: false,
},
{
desc: "bitbucket only repository",
token: &ProvisionTokenV2{
Metadata: Metadata{
Name: "test",
},
Spec: ProvisionTokenSpecV2{
Roles: []SystemRole{RoleNode},
JoinMethod: JoinMethodBitbucket,
Bitbucket: &ProvisionTokenSpecV2Bitbucket{
Audience: "foo",
IdentityProviderURL: "https://example.com",
Allow: []*ProvisionTokenSpecV2Bitbucket_Rule{
{
RepositoryUUID: "{foo}",
},
},
},
},
},
wantErr: false,
},
{
desc: "bitbucket missing audience",
token: &ProvisionTokenV2{
Metadata: Metadata{
Name: "test",
},
Spec: ProvisionTokenSpecV2{
Roles: []SystemRole{RoleNode},
JoinMethod: JoinMethodBitbucket,
Bitbucket: &ProvisionTokenSpecV2Bitbucket{
IdentityProviderURL: "https://example.com",
Allow: []*ProvisionTokenSpecV2Bitbucket_Rule{
{
WorkspaceUUID: "{foo}",
},
},
},
},
},
wantErr: true,
},
{
desc: "bitbucket missing identity provider",
token: &ProvisionTokenV2{
Metadata: Metadata{
Name: "test",
},
Spec: ProvisionTokenSpecV2{
Roles: []SystemRole{RoleNode},
JoinMethod: JoinMethodBitbucket,
Bitbucket: &ProvisionTokenSpecV2Bitbucket{
Audience: "foo",
Allow: []*ProvisionTokenSpecV2Bitbucket_Rule{
{
WorkspaceUUID: "{foo}",
},
},
},
},
},
wantErr: true,
},
{
desc: "bitbucket missing workspace or repository",
token: &ProvisionTokenV2{
Metadata: Metadata{
Name: "test",
},
Spec: ProvisionTokenSpecV2{
Roles: []SystemRole{RoleNode},
JoinMethod: JoinMethodBitbucket,
Bitbucket: &ProvisionTokenSpecV2Bitbucket{
Audience: "foo",
IdentityProviderURL: "https://example.com",
Allow: []*ProvisionTokenSpecV2Bitbucket_Rule{
{
DeploymentEnvironmentUUID: "{foo}",
},
},
},
},
},
wantErr: true,
},
}

for _, tc := range testcases {
Expand Down
Loading

0 comments on commit 5bfc4d8

Please sign in to comment.