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

Authenticate 1Password CLI with biometric unlock using user account #119

Merged
merged 11 commits into from
Dec 11, 2023
Merged
57 changes: 41 additions & 16 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,51 @@ description: |-
Use the 1Password Terraform provider to reference, create, or update items in your existing vaults using [1Password Secrets Automation](https://1password.com/secrets).

The 1Password Terraform provider supports using both [1Password Connect Server](https://developer.1password.com/docs/secrets-automation/#1password-connect-server)
and [1Password Service Accounts](https://developer.1password.com/docs/secrets-automation/#1password-service-accounts).
To use a service account token, you must install [1Password CLI](https://developer.1password.com/docs/cli) on the machine running Terraform. Refer to the
and [1Password CLI](https://developer.1password.com/docs/cli).

You must install [1Password CLI](https://developer.1password.com/docs/cli) on the machine running Terraform to use it. Refer to the
[Terraform documentation](https://developer.hashicorp.com/terraform/cloud-docs/run/install-software#only-install-standalone-binaries) to learn how to install 1Password CLI on Terraform Cloud.

## Authenticate CLI with service account

To authenticate CLI with service account, set `service_account_token` in the provider configuration.

Retry mechanism is implemented when using provider with service account. Each retry fast forwards to the [service account rate limit](https://developer.1password.com/docs/service-accounts/rate-limits/).

It's recommended to limit the number of parallel resource operations. It can be done by using `-parallelism=n` flag when running `terraform apply`, where `n` is the number of parallel resource operations (the default is `10`).
```
terraform apply `-parallelism=n`
```

The reason of having retry mechanism is that 1Password doesn't allow parallel modification on the items located in the same vault.
volodymyrZotov marked this conversation as resolved.
Show resolved Hide resolved

## Authenticate CLI with user account using biometric unlock

To authenticate CLI with user account using biometric unlock:
volodymyrZotov marked this conversation as resolved.
Show resolved Hide resolved
1. [Turn on the app integration](https://developer.1password.com/docs/cli/app-integration/#step-1-turn-on-the-app-integration)
2. In the terminal run `op account ls` to find sign-in address or account ID. It will print similar output in the console:
```
URL EMAIL USER ID
acme.dev.com test.user@acme.com HERE_WILL_BE_REAL_USER_ID
acme.prod.com prod.user@acme.com HERE_WILL_BE_REAL_USER_ID
```
3. Set `account` in the provider configuration with the `URL` or `USER ID` value from the previous step.
4. When biometric unlock popup appears while running terraform command, [authenticate it using fingerprint or password](https://developer.1password.com/docs/cli/app-integration/#step-2-enter-any-command-to-sign-in).
volodymyrZotov marked this conversation as resolved.
Show resolved Hide resolved

## Use with 1Password Connect

To use provider with 1Password Connect you need to
volodymyrZotov marked this conversation as resolved.
Show resolved Hide resolved
1. [Deploy your Connect server](https://developer.1password.com/docs/connect/get-started#deployment)
2. Set `url` and `token` in the provider configuration.

## Example Usage

```terraform
provider "onepassword" {
url = "http://localhost:8080"
token = "CONNECT_TOKEN"
service_account_token = "SERVICE_ACCOUNT_TOKEN"
account = "ACCOUNT_ID_OR_SIGN_IN_ADDRESS"
op_cli_path = "OP_CLI_PATH"
}
```
Expand All @@ -30,17 +64,8 @@ provider "onepassword" {

### Optional

- `op_cli_path` (String) The path to the 1Password CLI binary. Can also be sourced from OP_CLI_PATH. Defaults to `op`. Only used when setting a `service_account_token`.
- `service_account_token` (String) A valid token for your 1Password Service Account. Can also be sourced from OP_SERVICE_ACCOUNT_TOKEN. Either this or `token` must be set.
- `token` (String) A valid token for your 1Password Connect API. Can also be sourced from OP_CONNECT_TOKEN. Either this or `service_account_token` must be set.
- `url` (String) The HTTP(S) URL where your 1Password Connect API can be found. Must be provided through the OP_CONNECT_HOST environment variable if this attribute is not set. Can be omitted, if service_account_token is set.

## Use with service accounts:
Retry mechanism is implemented when using provider with service accounts. Each retry fast forwards to the [service account rate limit](https://developer.1password.com/docs/service-accounts/rate-limits/).

It's recommended to limit the number of parallel resource operations. It can be done by using `-parallelism=n` flag when running `terraform apply`, where `n` is the number of parallel resource operations (the default is `10`).
```
terraform apply `-parallelism=n`
```

The reason of having retry mechanism is that 1Password doesn't allow parallel modification on the items located in the same vault.
- `account` (String) A valid account's sign-in address or ID to use biometrics unlock. Can also be sourced from OP_ACCOUNT. Must be set to use with biometric unlock.
- `op_cli_path` (String) The path to the 1Password CLI binary. Can also be sourced from OP_CLI_PATH. Defaults to `op`.
- `service_account_token` (String) A valid token for your 1Password Service Account. Can also be sourced from OP_SERVICE_ACCOUNT_TOKEN. Must be set to use with 1Password service account.
- `token` (String) A valid token for your 1Password Connect API. Can also be sourced from OP_CONNECT_TOKEN. Must be set to use with 1Password Connect server.
- `url` (String) The HTTP(S) URL where your 1Password Connect API can be found. Must be provided through the OP_CONNECT_HOST environment variable if this attribute is not set. Must be set to use with 1Password Connect server.
1 change: 1 addition & 0 deletions examples/provider/provider.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ provider "onepassword" {
url = "http://localhost:8080"
token = "CONNECT_TOKEN"
service_account_token = "SERVICE_ACCOUNT_TOKEN"
account = "ACCOUNT_ID_OR_SIGN_IN_ADDRESS"
op_cli_path = "OP_CLI_PATH"
}
17 changes: 15 additions & 2 deletions onepassword/cli/op.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ import (
type OP struct {
binaryPath string
serviceAccountToken string
account string
}

func New(serviceAccountToken, binaryPath string) *OP {
func New(serviceAccountToken, binaryPath, account string) *OP {
return &OP{
binaryPath: binaryPath,
serviceAccountToken: serviceAccountToken,
account: account,
}
}

Expand Down Expand Up @@ -174,18 +176,29 @@ func (op *OP) execJson(ctx context.Context, dst any, stdin []byte, args ...opArg

func (op *OP) execRaw(ctx context.Context, stdin []byte, args ...opArg) ([]byte, error) {
var cmdArgs []string

if op.account != "" {
args = append(args, f("account", op.account))
}

for _, arg := range args {
cmdArgs = append(cmdArgs, arg.format())
}

cmd := exec.CommandContext(ctx, op.binaryPath, cmdArgs...)
cmd.Env = append(cmd.Environ(),
"OP_SERVICE_ACCOUNT_TOKEN="+op.serviceAccountToken,
"OP_FORMAT=json",
"OP_INTEGRATION_NAME=terraform-provider-connect",
"OP_INTEGRATION_ID=GO",
//"OP_INTEGRATION_BUILDNUMBER="+version.ProviderVersion, // causes bad request errors from CLI
)
if op.serviceAccountToken != "" {
cmd.Env = append(cmd.Env, "OP_SERVICE_ACCOUNT_TOKEN="+op.serviceAccountToken)
}
if op.account != "" {
cmd.Env = append(cmd.Env, "OP_BIOMETRIC_UNLOCK_ENABLED=true")
}

if stdin != nil {
cmd.Stdin = bytes.NewReader(stdin)
}
Expand Down
54 changes: 36 additions & 18 deletions onepassword/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,31 @@ func Provider() *schema.Provider {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("OP_CONNECT_HOST", nil),
Description: "The HTTP(S) URL where your 1Password Connect API can be found. Must be provided through the OP_CONNECT_HOST environment variable if this attribute is not set. Can be omitted, if service_account_token is set.",
Description: "The HTTP(S) URL where your 1Password Connect API can be found. Must be provided through the OP_CONNECT_HOST environment variable if this attribute is not set. Must be set to use with 1Password Connect server.",
},
"token": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("OP_CONNECT_TOKEN", nil),
Description: "A valid token for your 1Password Connect API. Can also be sourced from OP_CONNECT_TOKEN. Either this or `service_account_token` must be set.",
Description: "A valid token for your 1Password Connect API. Can also be sourced from OP_CONNECT_TOKEN. Must be set to use with 1Password Connect server.",
},
"service_account_token": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("OP_SERVICE_ACCOUNT_TOKEN", nil),
Description: "A valid token for your 1Password Service Account. Can also be sourced from OP_SERVICE_ACCOUNT_TOKEN. Either this or `token` must be set.",
Description: "A valid token for your 1Password Service Account. Can also be sourced from OP_SERVICE_ACCOUNT_TOKEN. Must be set to use with 1Password service account.",
},
"account": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("OP_ACCOUNT", nil),
Description: "A valid account's sign-in address or ID to use biometrics unlock. Can also be sourced from OP_ACCOUNT. Must be set to use with biometric unlock.",
},
"op_cli_path": {
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("OP_CLI_PATH", "op"),
Description: "The path to the 1Password CLI binary. Can also be sourced from OP_CLI_PATH. Defaults to `op`. Only used when setting a `service_account_token`.",
Description: "The path to the 1Password CLI binary. Can also be sourced from OP_CLI_PATH. Defaults to `op`.",
},
},
DataSourcesMap: map[string]*schema.Resource{
Expand All @@ -78,6 +84,7 @@ func Provider() *schema.Provider {
url = d.Get("url").(string)
token = d.Get("token").(string)
serviceAccountToken = d.Get("service_account_token").(string)
account = d.Get("account").(string)
opCliPath = d.Get("op_cli_path").(string)
)

Expand All @@ -86,34 +93,45 @@ func Provider() *schema.Provider {
// the other one is prompted for, but Terraform then forgets the value for the one that
// is defined in the code. This confusing user-experience can be avoided by handling the
// requirement of one of the attributes manually.
if serviceAccountToken != "" {
if serviceAccountToken != "" || account != "" {
if token != "" || url != "" {
return nil, diag.Errorf("Either Connect credentials (\"token\" and \"url\") or Service Account (\"service_account_token\") credentials can be set. Both are set. Please unset one of them.")
return nil, diag.Errorf("Either Connect credentials (\"token\" and \"url\") or 1Password CLI (\"service_account_token\" or \"account\") credentials can be set. Both are set. Please unset one of them.")
}
if opCliPath == "" {
return nil, diag.Errorf("Path to op CLI binary is not set. Either leave empty, provide the \"op_cli_path\" field in the provider configuration, or set the OP_CLI_PATH environment variable.")
}

op := cli.New(serviceAccountToken, opCliPath)

cliVersion, err := op.GetVersion(ctx)
if err != nil {
return nil, diag.FromErr(fmt.Errorf("failed to get version of op CLI: %w", err))
}
if cliVersion.LessThan(semver.MustParse(minimumOpCliVersion)) {
return nil, diag.Errorf("Current 1Password CLI version is \"%s\". Please upgrade to at least \"%s\".", cliVersion, minimumOpCliVersion)
}

return (Client)(op), nil
return initializeCLI(ctx, serviceAccountToken, account, opCliPath)
} else if token != "" && url != "" {
return connectctx.Wrap(connect.NewClientWithUserAgent(url, token, providerUserAgent)), nil
} else {
return nil, diag.Errorf("Invalid provider configuration. Either Connect credentials (\"token\" and \"url\") or Service Account (\"service_account_token\") credentials should be set.")
return nil, diag.Errorf("Invalid provider configuration. Either Connect credentials (\"token\" and \"url\") or Service Account (\"service_account_token\" or \"account\") credentials should be set.")
}
}
return provider
}

// initializeCLI initializes CLI to use either with service account or with user account
// service account takes preference if both are set
func initializeCLI(ctx context.Context, serviceAccountToken, account, opCliPath string) (Client, diag.Diagnostics) {
op := cli.New("", opCliPath, account)

// override OP to use service account token
if serviceAccountToken != "" {
op = cli.New(serviceAccountToken, opCliPath, "")
}

cliVersion, err := op.GetVersion(ctx)
if err != nil {
return nil, diag.FromErr(fmt.Errorf("failed to get version of op CLI: %w", err))
}
if cliVersion.LessThan(semver.MustParse(minimumOpCliVersion)) {
return nil, diag.Errorf("Current 1Password CLI version is \"%s\". Please upgrade to at least \"%s\".", cliVersion, minimumOpCliVersion)
}

return op, nil
}

// Client is a subset of connect.Client with context added.
type Client interface {
GetVault(ctx context.Context, uuid string) (*onepassword.Vault, error)
Expand Down
39 changes: 31 additions & 8 deletions templates/index.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,45 @@ description: |-
Use the 1Password Terraform provider to reference, create, or update items in your existing vaults using [1Password Secrets Automation](https://1password.com/secrets).

The 1Password Terraform provider supports using both [1Password Connect Server](https://developer.1password.com/docs/secrets-automation/#1password-connect-server)
and [1Password Service Accounts](https://developer.1password.com/docs/secrets-automation/#1password-service-accounts).
To use a service account token, you must install [1Password CLI](https://developer.1password.com/docs/cli) on the machine running Terraform. Refer to the
and [1Password CLI](https://developer.1password.com/docs/cli).

You must install [1Password CLI](https://developer.1password.com/docs/cli) on the machine running Terraform to use it. Refer to the
[Terraform documentation](https://developer.hashicorp.com/terraform/cloud-docs/run/install-software#only-install-standalone-binaries) to learn how to install 1Password CLI on Terraform Cloud.

## Example Usage
## Authenticate CLI with service account

{{tffile "examples/provider/provider.tf"}}

{{ .SchemaMarkdown | trimspace }}
To authenticate CLI with service account, set `service_account_token` in the provider configuration.

## Use with service accounts:
Retry mechanism is implemented when using provider with service accounts. Each retry fast forwards to the [service account rate limit](https://developer.1password.com/docs/service-accounts/rate-limits/).
Retry mechanism is implemented when using provider with service account. Each retry fast forwards to the [service account rate limit](https://developer.1password.com/docs/service-accounts/rate-limits/).

It's recommended to limit the number of parallel resource operations. It can be done by using `-parallelism=n` flag when running `terraform apply`, where `n` is the number of parallel resource operations (the default is `10`).
```
terraform apply `-parallelism=n`
```

The reason of having retry mechanism is that 1Password doesn't allow parallel modification on the items located in the same vault.

## Authenticate CLI with user account using biometric unlock
volodymyrZotov marked this conversation as resolved.
Show resolved Hide resolved

To authenticate CLI with user account using biometric unlock:
1. [Turn on the app integration](https://developer.1password.com/docs/cli/app-integration/#step-1-turn-on-the-app-integration)
2. In the terminal run `op account ls` to find sign-in address or account ID. It will print similar output in the console:
```
URL EMAIL USER ID
acme.dev.com test.user@acme.com HERE_WILL_BE_REAL_USER_ID
acme.prod.com prod.user@acme.com HERE_WILL_BE_REAL_USER_ID
```
3. Set `account` in the provider configuration with the `URL` or `USER ID` value from the previous step.
4. When biometric unlock popup appears while running terraform command, [authenticate it using fingerprint or password](https://developer.1password.com/docs/cli/app-integration/#step-2-enter-any-command-to-sign-in).
volodymyrZotov marked this conversation as resolved.
Show resolved Hide resolved

## Use with 1Password Connect

To use provider with 1Password Connect you need to
volodymyrZotov marked this conversation as resolved.
Show resolved Hide resolved
1. [Deploy your Connect server](https://developer.1password.com/docs/connect/get-started#deployment)
2. Set `url` and `token` in the provider configuration.

## Example Usage

{{tffile "examples/provider/provider.tf"}}

{{ .SchemaMarkdown | trimspace }}
Loading