diff --git a/docs/data-sources/argus_instance.md b/docs/data-sources/argus_instance.md index 5bbecec3..bd78960a 100644 --- a/docs/data-sources/argus_instance.md +++ b/docs/data-sources/argus_instance.md @@ -35,7 +35,7 @@ data "stackit_argus_instance" "example" { - `grafana_initial_admin_user` (String) Specifies an initial Grafana admin username. - `grafana_public_read_access` (Boolean) If true, anyone can access Grafana dashboards without logging in. - `grafana_url` (String) Specifies Grafana URL. -- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`instance_id`". +- `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`instance_id`". - `is_updatable` (Boolean) Specifies if the instance can be updated. - `jaeger_traces_url` (String) - `jaeger_ui_url` (String) diff --git a/docs/data-sources/argus_scrapeconfig.md b/docs/data-sources/argus_scrapeconfig.md index 5c95c9c4..c9fae808 100644 --- a/docs/data-sources/argus_scrapeconfig.md +++ b/docs/data-sources/argus_scrapeconfig.md @@ -32,7 +32,7 @@ data "stackit_argus_scrapeconfig" "example" { ### Read-Only - `basic_auth` (Attributes) A basic authentication block. (see [below for nested schema](#nestedatt--basic_auth)) -- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`instance_id`,`name`". +- `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`instance_id`,`name`". - `metrics_path` (String) Specifies the job scraping url path. - `saml2` (Attributes) A SAML2 configuration block (see [below for nested schema](#nestedatt--saml2)) - `scheme` (String) Specifies the http scheme. diff --git a/docs/data-sources/dns_record_set.md b/docs/data-sources/dns_record_set.md index 26089c4f..49b98368 100644 --- a/docs/data-sources/dns_record_set.md +++ b/docs/data-sources/dns_record_set.md @@ -34,7 +34,7 @@ data "stackit_dns_record_set" "example" { - `active` (Boolean) Specifies if the record set is active or not. - `comment` (String) Comment. - `error` (String) Error shows error in case create/update/delete failed. -- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`zone_id`,`record_set_id`". +- `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`zone_id`,`record_set_id`". - `name` (String) Name of the record which should be a valid domain according to rfc1035 Section 2.3.4. E.g. `example.com` - `records` (List of String) Records. - `state` (String) Record set state. diff --git a/docs/data-sources/dns_zone.md b/docs/data-sources/dns_zone.md index 175685cf..d78b49d6 100644 --- a/docs/data-sources/dns_zone.md +++ b/docs/data-sources/dns_zone.md @@ -36,7 +36,7 @@ data "stackit_dns_zone" "example" { - `description` (String) Description of the zone. - `dns_name` (String) The zone name. E.g. `example.com` - `expire_time` (Number) Expire time. -- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`zone_id`". +- `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`zone_id`". - `is_reverse_zone` (Boolean) Specifies, if the zone is a reverse zone or not. - `name` (String) The user given name of the zone. - `negative_cache` (Number) Negative caching. diff --git a/docs/data-sources/logme_credentials.md b/docs/data-sources/logme_credentials.md index 214cc448..29c3c768 100644 --- a/docs/data-sources/logme_credentials.md +++ b/docs/data-sources/logme_credentials.md @@ -34,7 +34,7 @@ data "stackit_logme_credentials" "example" { - `host` (String) - `hosts` (List of String) - `http_api_uri` (String) -- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`instance_id`,`credentials_id`". +- `id` (String) Terraform's internal data source. identifier. It is structured as "`project_id`,`instance_id`,`credentials_id`". - `name` (String) - `password` (String, Sensitive) - `port` (Number) diff --git a/docs/data-sources/logme_instance.md b/docs/data-sources/logme_instance.md index f232f61b..2ed44f10 100644 --- a/docs/data-sources/logme_instance.md +++ b/docs/data-sources/logme_instance.md @@ -33,7 +33,7 @@ data "stackit_logme_instance" "example" { - `cf_organization_guid` (String) - `cf_space_guid` (String) - `dashboard_url` (String) -- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`instance_id`". +- `id` (String) Terraform's internal data source. identifier. It is structured as "`project_id`,`instance_id`". - `image_url` (String) - `name` (String) Instance name. - `parameters` (Attributes) (see [below for nested schema](#nestedatt--parameters)) diff --git a/docs/data-sources/mariadb_credentials.md b/docs/data-sources/mariadb_credentials.md index dff23677..87bc9de3 100644 --- a/docs/data-sources/mariadb_credentials.md +++ b/docs/data-sources/mariadb_credentials.md @@ -34,7 +34,7 @@ data "stackit_mariadb_credentials" "example" { - `host` (String) - `hosts` (List of String) - `http_api_uri` (String) -- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`instance_id`,`credentials_id`". +- `id` (String) Terraform's internal data source. identifier. It is structured as "`project_id`,`instance_id`,`credentials_id`". - `name` (String) - `password` (String, Sensitive) - `port` (Number) diff --git a/docs/data-sources/mariadb_instance.md b/docs/data-sources/mariadb_instance.md index e3af58c2..06bc0623 100644 --- a/docs/data-sources/mariadb_instance.md +++ b/docs/data-sources/mariadb_instance.md @@ -33,7 +33,7 @@ data "stackit_mariadb_instance" "example" { - `cf_organization_guid` (String) - `cf_space_guid` (String) - `dashboard_url` (String) -- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`instance_id`". +- `id` (String) Terraform's internal data source. identifier. It is structured as "`project_id`,`instance_id`". - `image_url` (String) - `name` (String) Instance name. - `parameters` (Attributes) (see [below for nested schema](#nestedatt--parameters)) diff --git a/docs/data-sources/objectstorage_bucket.md b/docs/data-sources/objectstorage_bucket.md new file mode 100644 index 00000000..223d1599 --- /dev/null +++ b/docs/data-sources/objectstorage_bucket.md @@ -0,0 +1,27 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_objectstorage_bucket Data Source - stackit" +subcategory: "" +description: |- + ObjectStorage credentials data source schema. +--- + +# stackit_objectstorage_bucket (Data Source) + +ObjectStorage credentials data source schema. + + + + +## Schema + +### Required + +- `bucket_name` (String) The bucket name. It must be DNS conform. +- `project_id` (String) STACKIT Project ID to which the bucket is associated. + +### Read-Only + +- `id` (String) Terraform's internal data source identifier. It is structured as "`project_id`,`bucket_name`". +- `url_path_style` (String) +- `url_virtual_hosted_style` (String) diff --git a/docs/data-sources/opensearch_credentials.md b/docs/data-sources/opensearch_credentials.md index 12428032..d6a80264 100644 --- a/docs/data-sources/opensearch_credentials.md +++ b/docs/data-sources/opensearch_credentials.md @@ -34,7 +34,7 @@ data "stackit_opensearch_credentials" "example" { - `host` (String) - `hosts` (List of String) - `http_api_uri` (String) -- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`instance_id`,`credentials_id`". +- `id` (String) Terraform's internal data source. identifier. It is structured as "`project_id`,`instance_id`,`credentials_id`". - `name` (String) - `password` (String, Sensitive) - `port` (Number) diff --git a/docs/data-sources/opensearch_instance.md b/docs/data-sources/opensearch_instance.md index 329f6b0b..0fd38c63 100644 --- a/docs/data-sources/opensearch_instance.md +++ b/docs/data-sources/opensearch_instance.md @@ -33,7 +33,7 @@ data "stackit_opensearch_instance" "example" { - `cf_organization_guid` (String) - `cf_space_guid` (String) - `dashboard_url` (String) -- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`instance_id`". +- `id` (String) Terraform's internal data source. identifier. It is structured as "`project_id`,`instance_id`". - `image_url` (String) - `name` (String) Instance name. - `parameters` (Attributes) (see [below for nested schema](#nestedatt--parameters)) diff --git a/docs/data-sources/postgresflex_instance.md b/docs/data-sources/postgresflex_instance.md index d9192675..5343acd9 100644 --- a/docs/data-sources/postgresflex_instance.md +++ b/docs/data-sources/postgresflex_instance.md @@ -32,7 +32,7 @@ data "stackit_postgresflex_instance" "example" { - `acl` (List of String) The Access Control List (ACL) for the PostgresFlex instance. - `backup_schedule` (String) - `flavor` (Attributes) (see [below for nested schema](#nestedatt--flavor)) -- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`instance_id`". +- `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`instance_id`". - `name` (String) Instance name. - `replicas` (Number) - `storage` (Attributes) (see [below for nested schema](#nestedatt--storage)) diff --git a/docs/data-sources/postgresflex_user.md b/docs/data-sources/postgresflex_user.md index 959ed6b3..721ed31c 100644 --- a/docs/data-sources/postgresflex_user.md +++ b/docs/data-sources/postgresflex_user.md @@ -32,8 +32,7 @@ data "stackit_postgresflex_user" "example" { ### Read-Only - `host` (String) -- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`instance_id`,`user_id`". -- `password` (String, Sensitive) +- `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`instance_id`,`user_id`". - `port` (Number) - `roles` (Set of String) - `username` (String) diff --git a/docs/data-sources/postgresql_credentials.md b/docs/data-sources/postgresql_credentials.md index 2db49d3c..d0c282d3 100644 --- a/docs/data-sources/postgresql_credentials.md +++ b/docs/data-sources/postgresql_credentials.md @@ -34,7 +34,7 @@ data "stackit_postgresql_credentials" "example" { - `host` (String) - `hosts` (List of String) - `http_api_uri` (String) -- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`instance_id`,`credentials_id`". +- `id` (String) Terraform's internal data source. identifier. It is structured as "`project_id`,`instance_id`,`credentials_id`". - `name` (String) - `password` (String, Sensitive) - `port` (Number) diff --git a/docs/data-sources/postgresql_instance.md b/docs/data-sources/postgresql_instance.md index aa6fa73e..3287c571 100644 --- a/docs/data-sources/postgresql_instance.md +++ b/docs/data-sources/postgresql_instance.md @@ -33,7 +33,7 @@ data "stackit_postgresql_instance" "example" { - `cf_organization_guid` (String) - `cf_space_guid` (String) - `dashboard_url` (String) -- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`instance_id`". +- `id` (String) Terraform's internal data source. identifier. It is structured as "`project_id`,`instance_id`". - `image_url` (String) - `name` (String) Instance name. - `parameters` (Attributes) (see [below for nested schema](#nestedatt--parameters)) diff --git a/docs/data-sources/rabbitmq_credentials.md b/docs/data-sources/rabbitmq_credentials.md index b7fa7a2c..b1ad93c8 100644 --- a/docs/data-sources/rabbitmq_credentials.md +++ b/docs/data-sources/rabbitmq_credentials.md @@ -34,7 +34,7 @@ data "stackit_rabbitmq_credentials" "example" { - `host` (String) - `hosts` (List of String) - `http_api_uri` (String) -- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`instance_id`,`credentials_id`". +- `id` (String) Terraform's internal data source. identifier. It is structured as "`project_id`,`instance_id`,`credentials_id`". - `name` (String) - `password` (String, Sensitive) - `port` (Number) diff --git a/docs/data-sources/rabbitmq_instance.md b/docs/data-sources/rabbitmq_instance.md index b5e5e143..49acd29b 100644 --- a/docs/data-sources/rabbitmq_instance.md +++ b/docs/data-sources/rabbitmq_instance.md @@ -33,7 +33,7 @@ data "stackit_rabbitmq_instance" "example" { - `cf_organization_guid` (String) - `cf_space_guid` (String) - `dashboard_url` (String) -- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`instance_id`". +- `id` (String) Terraform's internal data source. identifier. It is structured as "`project_id`,`instance_id`". - `image_url` (String) - `name` (String) Instance name. - `parameters` (Attributes) (see [below for nested schema](#nestedatt--parameters)) diff --git a/docs/data-sources/redis_credentials.md b/docs/data-sources/redis_credentials.md index 4c3cc5ac..da785305 100644 --- a/docs/data-sources/redis_credentials.md +++ b/docs/data-sources/redis_credentials.md @@ -34,7 +34,7 @@ data "stackit_redis_credentials" "example" { - `host` (String) - `hosts` (List of String) - `http_api_uri` (String) -- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`instance_id`,`credentials_id`". +- `id` (String) Terraform's internal data source. identifier. It is structured as "`project_id`,`instance_id`,`credentials_id`". - `name` (String) - `password` (String, Sensitive) - `port` (Number) diff --git a/docs/data-sources/redis_instance.md b/docs/data-sources/redis_instance.md index 2cf6de0b..70488472 100644 --- a/docs/data-sources/redis_instance.md +++ b/docs/data-sources/redis_instance.md @@ -33,7 +33,7 @@ data "stackit_redis_instance" "example" { - `cf_organization_guid` (String) - `cf_space_guid` (String) - `dashboard_url` (String) -- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`instance_id`". +- `id` (String) Terraform's internal data source. identifier. It is structured as "`project_id`,`instance_id`". - `image_url` (String) - `name` (String) Instance name. - `parameters` (Attributes) (see [below for nested schema](#nestedatt--parameters)) diff --git a/docs/data-sources/resourcemanager_project.md b/docs/data-sources/resourcemanager_project.md index f5b31b24..16376f9c 100644 --- a/docs/data-sources/resourcemanager_project.md +++ b/docs/data-sources/resourcemanager_project.md @@ -28,7 +28,7 @@ data "stackit_resourcemanager_project" "example" { ### Read-Only -- `id` (String) Terraform's internal resource ID. It is structured as "`container_id`". +- `id` (String) Terraform's internal data source. ID. It is structured as "`container_id`". - `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource container. A label key must match the regex [A-ZÄÜÖa-zäüöß0-9_-]{1,64}. A label value must match the regex ^$|[A-ZÄÜÖa-zäüöß0-9_-]{1,64} - `name` (String) Project name. - `parent_container_id` (String) Parent container ID diff --git a/docs/data-sources/ske_cluster.md b/docs/data-sources/ske_cluster.md index d363cac9..23038a34 100644 --- a/docs/data-sources/ske_cluster.md +++ b/docs/data-sources/ske_cluster.md @@ -34,7 +34,7 @@ data "stackit_ske_cluster" "example" { This should be used with care since it also disables a couple of other features like the use of some volume type (e.g. PVCs). - `extensions` (Attributes) A single extensions block as defined below (see [below for nested schema](#nestedatt--extensions)) - `hibernations` (Attributes List) One or more hibernation block as defined below. (see [below for nested schema](#nestedatt--hibernations)) -- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`name`". +- `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`name`". - `kube_config` (String, Sensitive) Kube config file used for connecting to the cluster - `kubernetes_version` (String) Kubernetes version. - `kubernetes_version_used` (String) Full Kubernetes version used. For example, if `1.22` was selected, this value may result to `1.22.15` diff --git a/docs/data-sources/ske_project.md b/docs/data-sources/ske_project.md index ef541a57..46b9a030 100644 --- a/docs/data-sources/ske_project.md +++ b/docs/data-sources/ske_project.md @@ -27,4 +27,4 @@ data "stackit_ske_project" "example" { ### Read-Only -- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`". +- `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`". diff --git a/docs/index.md b/docs/index.md index 7049b76f..389e9c4d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,11 +8,96 @@ The STACKIT provider is the official Terraform provider to integrate all the res provider "stackit" { region = "eu01" } + +# Authentication + +# Token flow +provider "stackit" { + region = "eu01" + service_account_token = var.service_account_token +} + +# Key flow +provider "stackit" { + region = "eu01" + service_account_key = var.service_account_key + private_key = var.private_key +} + +# Key flow (using path) +provider "stackit" { + region = "eu01" + service_account_key_path = var.service_account_key_path + private_key_path = var.private_key_path +} ``` ## Authentication -Currently, only the *token flow* is supported. The Terraform provider will first try to find a token in the `STACKIT_SERVICE_ACCOUNT_TOKEN` env var. If not present, it will check the credentials file located in the path defined by the `STACKIT_CREDENTIALS_PATH` env var, if specified, or in `$HOME/.stackit/credentials.json` as a fallback. If the token is found, all the requests are authenticated using that token. +To authenticate, you will need a [service account](https://docs.stackit.cloud/stackit/en/service-accounts-134415819.html). Create it in the STACKIT Portal an assign it the necessary permissions, e.g. `project.owner`. There are multiple ways to authenticate: + +- Key flow (recommended) +- Token flow + +When setting up authentication, the provider will always try to use the key flow first and search for credentials in several locations, following a specific order: + +1. Explicit configuration, e.g. by seting the fiel `stackit_service_account_key_path` in the provider block (see example below) +2. Environment variable, e.g. by setting `STACKIT_SERVICE_ACCOUNT_KEY_PATH` +3. Credentials file + + The SDK will check the credentials file located in the path defined by the `STACKIT_CREDENTIALS_PATH` env var, if specified, + or in `$HOME/.stackit/credentials.json` as a fallback. + The credentials should be set using the same name as the environmnet variables. Example: + + ```json + { + "STACKIT_SERVICE_ACCOUNT_TOKEN": "foo_token", + "STACKIT_SERVICE_ACCOUNT_KEY_PATH": "path/to/sa_key.json", + "STACKIT_PRIVATE_KEY_PATH": "path/to/private_key.pem" + } + ``` + +### Key flow + +To use the key flow, you need to have a service account key and an RSA key-pair. +To configure it, follow this steps: + + The following instructions assume that you have created a service account and assigned it the necessary permissions, e.g. project.owner. + +1. In the Portal, go to `Service Account -> Service Account Keys` and create a key. + - You can create your own RSA key-pair or have the Portal generate one for you. +2. Save the content of the service account key and the corresponding private key by copying them or saving them in a file. The expected format of the service account key is the following: + ```json + { + "id": "uuid", + "publicKey": "public key", + "createdAt": "2023-08-24T14:15:22Z", + "validUntil": "2023-08-24T14:15:22Z", + "keyType": "USER_MANAGED", + "keyOrigin": "USER_PROVIDED", + "keyAlgorithm": "RSA_2048", + "active": true, + "credentials": { + "kid": "string", + "iss": "my-sa@sa.stackit.cloud", + "sub": "uuid", + "aud": "string", + (optional) "privateKey": "private key when generated by the SA service" + } + } + ``` +3. Configure the service account key and private key for authentication in the SDK: + - setting the fiels in the provider block: `service_account_key` or `service_account_key_path`, `private_key` or `private_key_path` + - setting environment variables: `STACKIT_SERVICE_ACCOUNT_KEY_PATH` and `STACKIT_PRIVATE_KEY_PATH` + - setting them in the credentials file (see above) + +### Token flow + +Using this flow is less secure since the token is long-lived. You can provide the token in several ways: + +1. Setting the field `service_account_token` in the provider +2. Setting the environment variable `STACKIT_SERVICE_ACCOUNT_TOKEN` +3. Setting it in the credentials file (see above) ## Schema @@ -22,15 +107,22 @@ Currently, only the *token flow* is supported. The Terraform provider will first - `argus_custom_endpoint` (String) Custom endpoint for the Argus service - `credentials_path` (String) Path of JSON from where the credentials are read. Takes precedence over the env var `STACKIT_CREDENTIALS_PATH`. Default value is `~/.stackit/credentials.json`. - `dns_custom_endpoint` (String) Custom endpoint for the DNS service +- `jwks_custom_endpoint` (String) Custom endpoint for the jwks API, which is used to get the json web key sets (jwks) to validate tokens when using the key flow - `logme_custom_endpoint` (String) Custom endpoint for the LogMe service - `mariadb_custom_endpoint` (String) Custom endpoint for the MariaDB service +- `objectstorage_custom_endpoint` (String) Custom endpoint for the Object Storage service - `opensearch_custom_endpoint` (String) Custom endpoint for the OpenSearch service - `postgresflex_custom_endpoint` (String) Custom endpoint for the PostgresFlex service - `postgresql_custom_endpoint` (String) Custom endpoint for the PostgreSQL service +- `private_key` (String) Private RSA key used for authentication. If set alongside the service account key, the key flow will be used to authenticate all operations. +- `private_key_path` (String) Path for the private RSA key used for authentication. If set alongside the service account key, the key flow will be used to authenticate all operations. - `rabbitmq_custom_endpoint` (String) Custom endpoint for the RabbitMQ service - `redis_custom_endpoint` (String) - `region` (String) Region will be used as the default location for regional services. Not all services require a region, some are global - `resourcemanager_custom_endpoint` (String) Custom endpoint for the Resource Manager service - `service_account_email` (String) Service account email. It can also be set using the environment variable STACKIT_SERVICE_ACCOUNT_EMAIL +- `service_account_key` (String) Service account key used for authentication. If set alongside private key, the key flow will be used to authenticate all operations. +- `service_account_key_path` (String) Path for the service account key used for authentication. If set alongside the private key, the key flow will be used to authenticate all operations. - `service_account_token` (String) Token used for authentication. If set, the token flow will be used to authenticate all operations. - `ske_custom_endpoint` (String) Custom endpoint for the Kubernetes Engine (SKE) service +- `token_custom_endpoint` (String) Custom endpoint for the token API, which is used to request access tokens when using the key flow diff --git a/docs/resources/objectstorage_bucket.md b/docs/resources/objectstorage_bucket.md new file mode 100644 index 00000000..c26842a9 --- /dev/null +++ b/docs/resources/objectstorage_bucket.md @@ -0,0 +1,27 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_objectstorage_bucket Resource - stackit" +subcategory: "" +description: |- + ObjectStorage bucket resource schema. +--- + +# stackit_objectstorage_bucket (Resource) + +ObjectStorage bucket resource schema. + + + + +## Schema + +### Required + +- `bucket_name` (String) The bucket name. It must be DNS conform. +- `project_id` (String) STACKIT Project ID to which the bucket is associated. + +### Read-Only + +- `id` (String) Terraform's internal resource identifier. It is structured as "`project_id`,`bucket_name`". +- `url_path_style` (String) +- `url_virtual_hosted_style` (String) diff --git a/examples/provider/provider.tf b/examples/provider/provider.tf index 61cf15e1..9b82f008 100644 --- a/examples/provider/provider.tf +++ b/examples/provider/provider.tf @@ -1,3 +1,26 @@ provider "stackit" { region = "eu01" } + +# Authentication + +# Token flow +provider "stackit" { + region = "eu01" + service_account_token = var.service_account_token +} + +# Key flow +provider "stackit" { + region = "eu01" + service_account_key = var.service_account_key + private_key = var.private_key +} + +# Key flow (using path) +provider "stackit" { + region = "eu01" + service_account_key_path = var.service_account_key_path + private_key_path = var.private_key_path +} + diff --git a/go.mod b/go.mod index 1bac4204..3d438c8c 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/hashicorp/terraform-plugin-go v0.19.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.5.1 - github.com/stackitcloud/stackit-sdk-go/core v0.2.0 + github.com/stackitcloud/stackit-sdk-go/core v0.3.0 github.com/stackitcloud/stackit-sdk-go/services/argus v0.3.0 github.com/stackitcloud/stackit-sdk-go/services/dns v0.3.0 github.com/stackitcloud/stackit-sdk-go/services/logme v0.4.0 @@ -27,12 +27,14 @@ require ( ) require ( + github.com/MicahParks/keyfunc/v2 v2.1.0 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 // indirect github.com/agext/levenshtein v1.2.2 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/fatih/color v1.13.0 // indirect github.com/go-logr/logr v1.2.4 // indirect + github.com/golang-jwt/jwt/v5 v5.0.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect diff --git a/go.sum b/go.sum index f0aa5d50..27c8bff2 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +github.com/MicahParks/keyfunc/v2 v2.1.0 h1:6ZXKb9Rp6qp1bDbJefnG7cTH8yMN1IC/4nf+GVjO99k= +github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 h1:KLq8BE0KwCL+mmXnjLWEAOYO+2l2AE4YMmqG1ZpZHBs= github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= @@ -26,6 +28,8 @@ github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -121,8 +125,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM= -github.com/stackitcloud/stackit-sdk-go/core v0.2.0 h1:dv9pMtGN6p5HbbpaB1PjncOdDPDyP4mXfXBxk7j9x0c= -github.com/stackitcloud/stackit-sdk-go/core v0.2.0/go.mod h1:Bne56SlX8V2GbONcofEmZz5lVLuuZgsc015FPIUYXy4= +github.com/stackitcloud/stackit-sdk-go/core v0.3.0 h1:bo29K6XMgUFhnvLJZ0dd/hWAltuDU+Qli7WqnKd2FbM= +github.com/stackitcloud/stackit-sdk-go/core v0.3.0/go.mod h1:gNsJT/70wQ0hNgbObqlKl8Y9SQVg8crnFLwqdzFF8Q0= github.com/stackitcloud/stackit-sdk-go/services/argus v0.3.0 h1:hwoTnlrD363H7Ois+7UkqmGivBrmRukPtfhFZEJT1XA= github.com/stackitcloud/stackit-sdk-go/services/argus v0.3.0/go.mod h1:1jD+OlbQjscg5ReF3I0kCgbQVR4YzfR6V0Lz5SiXja8= github.com/stackitcloud/stackit-sdk-go/services/dns v0.3.0 h1:XGVY9pV9qLSgoXHCjA7WpKG8zMvdPFjyeqqGmGqGVpw= diff --git a/stackit/provider.go b/stackit/provider.go index 9df61f9d..deda860c 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -66,6 +66,10 @@ func (p *Provider) Metadata(_ context.Context, _ provider.MetadataRequest, resp type providerModel struct { CredentialsFilePath types.String `tfsdk:"credentials_path"` ServiceAccountEmail types.String `tfsdk:"service_account_email"` + ServiceAccountKey types.String `tfsdk:"service_account_key"` + ServiceAccountKeyPath types.String `tfsdk:"service_account_key_path"` + PrivateKey types.String `tfsdk:"private_key"` + PrivateKeyPath types.String `tfsdk:"private_key_path"` Token types.String `tfsdk:"service_account_token"` Region types.String `tfsdk:"region"` DNSCustomEndpoint types.String `tfsdk:"dns_custom_endpoint"` @@ -80,6 +84,8 @@ type providerModel struct { ArgusCustomEndpoint types.String `tfsdk:"argus_custom_endpoint"` SKECustomEndpoint types.String `tfsdk:"ske_custom_endpoint"` ResourceManagerCustomEndpoint types.String `tfsdk:"resourcemanager_custom_endpoint"` + TokenCustomEndpoint types.String `tfsdk:"token_custom_endpoint"` + JWKSCustomEndpoint types.String `tfsdk:"jwks_custom_endpoint"` } // Schema defines the provider-level schema for configuration data. @@ -87,6 +93,10 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro descriptions := map[string]string{ "credentials_path": "Path of JSON from where the credentials are read. Takes precedence over the env var `STACKIT_CREDENTIALS_PATH`. Default value is `~/.stackit/credentials.json`.", "service_account_token": "Token used for authentication. If set, the token flow will be used to authenticate all operations.", + "service_account_key_path": "Path for the service account key used for authentication. If set alongside the private key, the key flow will be used to authenticate all operations.", + "service_account_key": "Service account key used for authentication. If set alongside private key, the key flow will be used to authenticate all operations.", + "private_key_path": "Path for the private RSA key used for authentication. If set alongside the service account key, the key flow will be used to authenticate all operations.", + "private_key": "Private RSA key used for authentication. If set alongside the service account key, the key flow will be used to authenticate all operations.", "service_account_email": "Service account email. It can also be set using the environment variable STACKIT_SERVICE_ACCOUNT_EMAIL", "region": "Region will be used as the default location for regional services. Not all services require a region, some are global", "dns_custom_endpoint": "Custom endpoint for the DNS service", @@ -100,6 +110,8 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro "argus_custom_endpoint": "Custom endpoint for the Argus service", "ske_custom_endpoint": "Custom endpoint for the Kubernetes Engine (SKE) service", "resourcemanager_custom_endpoint": "Custom endpoint for the Resource Manager service", + "token_custom_endpoint": "Custom endpoint for the token API, which is used to request access tokens when using the key flow", + "jwks_custom_endpoint": "Custom endpoint for the jwks API, which is used to get the json web key sets (jwks) to validate tokens when using the key flow", } resp.Schema = schema.Schema{ @@ -116,6 +128,22 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro Optional: true, Description: descriptions["service_account_token"], }, + "service_account_key_path": schema.StringAttribute{ + Optional: true, + Description: descriptions["service_account_key_path"], + }, + "service_account_key": schema.StringAttribute{ + Optional: true, + Description: descriptions["service_account_key"], + }, + "private_key": schema.StringAttribute{ + Optional: true, + Description: descriptions["private_key"], + }, + "private_key_path": schema.StringAttribute{ + Optional: true, + Description: descriptions["private_key_path"], + }, "region": schema.StringAttribute{ Optional: true, Description: descriptions["region"], @@ -168,6 +196,14 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro Optional: true, Description: descriptions["resourcemanager_custom_endpoint"], }, + "token_custom_endpoint": schema.StringAttribute{ + Optional: true, + Description: descriptions["token_custom_endpoint"], + }, + "jwks_custom_endpoint": schema.StringAttribute{ + Optional: true, + Description: descriptions["jwks_custom_endpoint"], + }, }, } } @@ -192,6 +228,18 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, providerData.ServiceAccountEmail = providerConfig.ServiceAccountEmail.ValueString() sdkConfig.ServiceAccountEmail = providerConfig.ServiceAccountEmail.ValueString() } + if !(providerConfig.ServiceAccountKey.IsUnknown() || providerConfig.ServiceAccountKey.IsNull()) { + sdkConfig.ServiceAccountKey = providerConfig.ServiceAccountKey.ValueString() + } + if !(providerConfig.ServiceAccountKeyPath.IsUnknown() || providerConfig.ServiceAccountKeyPath.IsNull()) { + sdkConfig.ServiceAccountKeyPath = providerConfig.ServiceAccountKeyPath.ValueString() + } + if !(providerConfig.PrivateKey.IsUnknown() || providerConfig.PrivateKey.IsNull()) { + sdkConfig.PrivateKey = providerConfig.PrivateKey.ValueString() + } + if !(providerConfig.PrivateKeyPath.IsUnknown() || providerConfig.PrivateKeyPath.IsNull()) { + sdkConfig.PrivateKeyPath = providerConfig.PrivateKeyPath.ValueString() + } if !(providerConfig.Token.IsUnknown() || providerConfig.Token.IsNull()) { sdkConfig.Token = providerConfig.Token.ValueString() } @@ -234,6 +282,12 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, if !(providerConfig.ResourceManagerCustomEndpoint.IsUnknown() || providerConfig.ResourceManagerCustomEndpoint.IsNull()) { providerData.ResourceManagerCustomEndpoint = providerConfig.ResourceManagerCustomEndpoint.ValueString() } + if !(providerConfig.TokenCustomEndpoint.IsUnknown() || providerConfig.TokenCustomEndpoint.IsNull()) { + sdkConfig.TokenCustomUrl = providerConfig.TokenCustomEndpoint.ValueString() + } + if !(providerConfig.JWKSCustomEndpoint.IsUnknown() || providerConfig.JWKSCustomEndpoint.IsNull()) { + sdkConfig.JWKSCustomUrl = providerConfig.JWKSCustomEndpoint.ValueString() + } roundTripper, err := sdkauth.SetupAuth(sdkConfig) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring provider", fmt.Sprintf("Setting up authentication: %v", err)) diff --git a/templates/index.md.tmpl b/templates/index.md.tmpl index ef8ed9e9..29eb8f35 100644 --- a/templates/index.md.tmpl +++ b/templates/index.md.tmpl @@ -8,6 +8,69 @@ The STACKIT provider is the official Terraform provider to integrate all the res ## Authentication -Currently, only the *token flow* is supported. The Terraform provider will first try to find a token in the `STACKIT_SERVICE_ACCOUNT_TOKEN` env var. If not present, it will check the credentials file located in the path defined by the `STACKIT_CREDENTIALS_PATH` env var, if specified, or in `$HOME/.stackit/credentials.json` as a fallback. If the token is found, all the requests are authenticated using that token. +To authenticate, you will need a [service account](https://docs.stackit.cloud/stackit/en/service-accounts-134415819.html). Create it in the STACKIT Portal an assign it the necessary permissions, e.g. `project.owner`. There are multiple ways to authenticate: + +- Key flow (recommended) +- Token flow + +When setting up authentication, the provider will always try to use the key flow first and search for credentials in several locations, following a specific order: + +1. Explicit configuration, e.g. by seting the fiel `stackit_service_account_key_path` in the provider block (see example below) +2. Environment variable, e.g. by setting `STACKIT_SERVICE_ACCOUNT_KEY_PATH` +3. Credentials file + + The SDK will check the credentials file located in the path defined by the `STACKIT_CREDENTIALS_PATH` env var, if specified, + or in `$HOME/.stackit/credentials.json` as a fallback. + The credentials should be set using the same name as the environmnet variables. Example: + + ```json + { + "STACKIT_SERVICE_ACCOUNT_TOKEN": "foo_token", + "STACKIT_SERVICE_ACCOUNT_KEY_PATH": "path/to/sa_key.json", + "STACKIT_PRIVATE_KEY_PATH": "path/to/private_key.pem" + } + ``` + +### Key flow + +To use the key flow, you need to have a service account key and an RSA key-pair. +To configure it, follow this steps: + + The following instructions assume that you have created a service account and assigned it the necessary permissions, e.g. project.owner. + +1. In the Portal, go to `Service Account -> Service Account Keys` and create a key. + - You can create your own RSA key-pair or have the Portal generate one for you. +2. Save the content of the service account key and the corresponding private key by copying them or saving them in a file. The expected format of the service account key is the following: + ```json + { + "id": "uuid", + "publicKey": "public key", + "createdAt": "2023-08-24T14:15:22Z", + "validUntil": "2023-08-24T14:15:22Z", + "keyType": "USER_MANAGED", + "keyOrigin": "USER_PROVIDED", + "keyAlgorithm": "RSA_2048", + "active": true, + "credentials": { + "kid": "string", + "iss": "my-sa@sa.stackit.cloud", + "sub": "uuid", + "aud": "string", + (optional) "privateKey": "private key when generated by the SA service" + } + } + ``` +3. Configure the service account key and private key for authentication in the SDK: + - setting the fiels in the provider block: `service_account_key` or `service_account_key_path`, `private_key` or `private_key_path` + - setting environment variables: `STACKIT_SERVICE_ACCOUNT_KEY_PATH` and `STACKIT_PRIVATE_KEY_PATH` + - setting them in the credentials file (see above) + +### Token flow + +Using this flow is less secure since the token is long-lived. You can provide the token in several ways: + +1. Setting the field `service_account_token` in the provider +2. Setting the environment variable `STACKIT_SERVICE_ACCOUNT_TOKEN` +3. Setting it in the credentials file (see above) {{ .SchemaMarkdown | trimspace }}