-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Machine ID: Terraform Cloud joining #45574
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
Changes from all commits
44e8219
ef74340
b4eb14c
f2ff62d
60be95b
8b1037f
92821b1
577b77c
dda5e83
b51201f
84bca00
9c6572b
dd86ca2
813c482
9fd4135
d6f4b19
709029f
2114881
edf14f2
99c0ab3
6d3de39
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1296,6 +1296,8 @@ message ProvisionTokenSpecV2 { | |
| ProvisionTokenSpecV2Spacelift Spacelift = 14 [(gogoproto.jsontag) = "spacelift,omitempty"]; | ||
| // TPM allows the configuration of options specific to the "tpm" join method. | ||
| 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"]; | ||
| } | ||
|
|
||
| // ProvisionTokenSpecV2TPM contains the TPM-specific part of the | ||
|
|
@@ -1580,6 +1582,55 @@ message ProvisionTokenSpecV2GCP { | |
| repeated Rule Allow = 1 [(gogoproto.jsontag) = "allow,omitempty"]; | ||
| } | ||
|
|
||
| // ProvisionTokenSpecV2Terraform contains Terraform-specific parts of the | ||
| // ProvisionTokenSpecV2. | ||
| message ProvisionTokenSpecV2TerraformCloud { | ||
| // Rule is a set of properties the Terraform-issued token might have to be | ||
| // allowed to use this ProvisionToken. | ||
| message Rule { | ||
| // OrganizationID is the ID of the HCP Terraform organization. At least | ||
| // one organization value is required, either ID or name. | ||
| string OrganizationID = 1 [(gogoproto.jsontag) = "organization_id,omitempty"]; | ||
|
|
||
| // OrganizationName is the human-readable name of the HCP Terraform | ||
| // organization. At least one organization value is required, either ID or | ||
| // name. | ||
| string OrganizationName = 2 [(gogoproto.jsontag) = "organization_name,omitempty"]; | ||
|
|
||
| // ProjectID is the ID of the HCP Terraform project. At least one project or | ||
| // workspace value is required, either ID or name. | ||
| string ProjectID = 3 [(gogoproto.jsontag) = "project_id,omitempty"]; | ||
|
|
||
| // ProjectName is the human-readable name for the HCP Terraform project. At | ||
| // least one project or workspace value is required, either ID or name. | ||
| string ProjectName = 4 [(gogoproto.jsontag) = "project_name,omitempty"]; | ||
|
|
||
| // WorkspaceID is the ID of the HCP Terraform workspace. At least one | ||
| // project or workspace value is required, either ID or name. | ||
| string WorkspaceID = 5 [(gogoproto.jsontag) = "workspace_id,omitempty"]; | ||
|
|
||
| // WorkspaceName is the human-readable name of the HCP Terraform workspace. | ||
| // At least one project or workspace value is required, either ID or name. | ||
| string WorkspaceName = 6 [(gogoproto.jsontag) = "workspace_name,omitempty"]; | ||
|
|
||
| // RunPhase is the phase of the run the token was issued for, e.g. `plan` or | ||
| // `apply` | ||
| string RunPhase = 7 [(gogoproto.jsontag) = "run_phase,omitempty"]; | ||
|
marcoandredinis marked this conversation as resolved.
|
||
| } | ||
|
|
||
| // 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 the JWT audience as configured in the | ||
| // TFC_WORKLOAD_IDENTITY_AUDIENCE(_$TAG) variable in Terraform Cloud. If | ||
| // unset, defaults to the Teleport cluster name. | ||
|
timothyb89 marked this conversation as resolved.
|
||
| // For example, if `TFC_WORKLOAD_IDENTITY_AUDIENCE_TELEPORT=foo` is set in | ||
| // Terraform Cloud, this value should be `foo`. If the variable is set to | ||
| // match the cluster name, it does not need to be set here. | ||
| string Audience = 2 [(gogoproto.jsontag) = "audience,omitempty"]; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we have it so that we always just expect the audience to equal the name of the Teleport cluster ? That sounds more semantically correct to me.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could, and I think that's the only answer we could bake in that makes sense. It might make the config/docs UX a bit weird (without a guided flow of some sort at least) but is otherwise fine. The more confusing parameter is the Do you think that should be the default value if unset, or should we just always use the cluster name? Is there any sane usecase for allowing user override? |
||
| } | ||
|
|
||
| // StaticTokensV2 implements the StaticTokens interface. | ||
| message StaticTokensV2 { | ||
| option (gogoproto.goproto_stringer) = false; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -71,6 +71,9 @@ const ( | |
| // JoinMethodTPM indicates that the node will join with the TPM join method. | ||
| // The core implementation of this join method can be found in lib/tpm. | ||
| JoinMethodTPM JoinMethod = "tpm" | ||
| // JoinMethodTerraformCloud indicates that the node will join using the Terraform | ||
| // join method. See lib/terraformcloud for more. | ||
| JoinMethodTerraformCloud JoinMethod = "terraform_cloud" | ||
| ) | ||
|
|
||
| var JoinMethods = []JoinMethod{ | ||
|
|
@@ -85,6 +88,7 @@ var JoinMethods = []JoinMethod{ | |
| JoinMethodSpacelift, | ||
| JoinMethodToken, | ||
| JoinMethodTPM, | ||
| JoinMethodTerraformCloud, | ||
| } | ||
|
|
||
| func ValidateJoinMethod(method JoinMethod) error { | ||
|
|
@@ -348,6 +352,17 @@ func (p *ProvisionTokenV2) CheckAndSetDefaults() error { | |
| if err := providerCfg.validate(); err != nil { | ||
| return trace.Wrap(err, "spec.tpm: failed validation") | ||
| } | ||
| case JoinMethodTerraformCloud: | ||
| providerCfg := p.Spec.TerraformCloud | ||
| if providerCfg == nil { | ||
| return trace.BadParameter( | ||
| "spec.terraform_cloud: must be configured for the join method %q", | ||
| JoinMethodTerraformCloud, | ||
| ) | ||
| } | ||
| if err := providerCfg.checkAndSetDefaults(); err != nil { | ||
| return trace.Wrap(err, "spec.terraform_cloud: failed validation") | ||
| } | ||
| default: | ||
| return trace.BadParameter("unknown join method %q", p.Spec.JoinMethod) | ||
| } | ||
|
|
@@ -817,3 +832,33 @@ func (a *ProvisionTokenSpecV2TPM) validate() error { | |
| } | ||
| return nil | ||
| } | ||
|
|
||
| func (a *ProvisionTokenSpecV2TerraformCloud) checkAndSetDefaults() error { | ||
| if len(a.Allow) == 0 { | ||
| return trace.BadParameter("the %q join method requires at least one token allow rule", JoinMethodTerraformCloud) | ||
| } | ||
|
|
||
| // Note: an empty audience will fall back to the cluster name. | ||
|
|
||
| for i, allowRule := range a.Allow { | ||
| orgSet := allowRule.OrganizationID != "" || allowRule.OrganizationName != "" | ||
| projectSet := allowRule.ProjectID != "" || allowRule.ProjectName != "" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are project names globally unique across TfCloud ? We should ensure we require at least one globally unique property here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Organization names/ids are, at least. The others are only unique within their parent org/project. I think we could require that at least organization (name or ID) is set, then at least one of project name/project ID/workspace name/workspace ID?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 I think I've done similar on another one of the join methods (always required org id/org name). Without it, it's super easy to make a join token that would allow a bad actor to create their own org, create a project w/ the same name, and access your cluster. |
||
| workspaceSet := allowRule.WorkspaceID != "" || allowRule.WorkspaceName != "" | ||
|
|
||
| if !orgSet { | ||
| return trace.BadParameter( | ||
| "allow[%d]: one of ['organization_id', 'organization_name'] must be set", | ||
| i, | ||
| ) | ||
| } | ||
|
|
||
| if !projectSet && !workspaceSet { | ||
| return trace.BadParameter( | ||
| "allow[%d]: at least one of ['project_id', 'project_name', 'workspace_id', 'workspace_name'] must be set", | ||
| i, | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.