Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

IAM Permissions Used

cluster-api-provider-stackit authenticates to STACKIT with the service account JSON stored in the StackitCluster.spec.credentialsSecretRef Secret. That service account should use a custom project role with only the permissions needed by the infrastructure provider.

Do not use broad project administrator roles for the controller. STACKIT documents custom roles as the way to bundle an explicit permission set, and role bindings as the way to assign that role to a user or service account.

Required provider permissions

The current provider implementation uses these STACKIT API operations:

Provider actionCode pathSTACKIT permission
Read the configured networkGetNetworkiaas.network.get
Create VM instancesCreateServeriaas.server.create
Find existing tagged VM instancesListServersiaas.server.list
Read VM stateGetServeriaas.server.get
Read VM NIC addresses for CAPI addresses and load balancer targetsListServerNICsiaas.server.nic.list
Delete VM instancesDeleteServeriaas.server.delete
Create the optional bastion public IPCreatePublicIPiaas.public-ip.create
Find existing tagged bastion public IPsListPublicIPsiaas.public-ip.list
Read the bastion public IP after attachGetPublicIPiaas.public-ip.get
Attach the bastion public IP to the bastion serverAddPublicIpToServeriaas.server.public-ip.add
Detach the bastion public IP during cleanupRemovePublicIpFromServeriaas.server.public-ip.remove
Delete the optional bastion public IPDeletePublicIPiaas.public-ip.delete
Create the optional bastion security groupCreateSecurityGroupiaas.security-group.create
Find existing tagged bastion security groupsListSecurityGroupsiaas.security-group.list
Create bastion SSH ingress rulesCreateSecurityGroupRuleiaas.security-group.rule.create
List bastion SSH ingress rulesListSecurityGroupRulesiaas.security-group.rule.list
Delete bastion SSH ingress rules during cleanupDeleteSecurityGroupRuleiaas.security-group.rule.delete
Attach the bastion security group to the bastion serverAddSecurityGroupToServeriaas.server.security-group.add
Detach the bastion security group during cleanupRemoveSecurityGroupFromServeriaas.server.security-group.remove
Delete the optional bastion security groupDeleteSecurityGroupiaas.security-group.delete
Create the API server network load balancerCreateLoadBalancernlb.loadbalancer.create
Find existing tagged load balancersListLoadBalancersnlb.loadbalancer.list
Read the load balancer before target updatesGetLoadBalancernlb.loadbalancer.get
Delete the API server load balancerDeleteLoadBalancernlb.loadbalancer.delete
Replace the API server target poolUpdateTargetPoolnlb.targetpool.replace

The least-privilege role for the current provider is therefore:

iaas.network.get
iaas.public-ip.create
iaas.public-ip.delete
iaas.public-ip.get
iaas.public-ip.list
iaas.server.create
iaas.server.delete
iaas.server.get
iaas.server.list
iaas.server.nic.list
iaas.server.public-ip.add
iaas.server.public-ip.remove
iaas.server.security-group.add
iaas.server.security-group.remove
iaas.security-group.create
iaas.security-group.delete
iaas.security-group.list
iaas.security-group.rule.create
iaas.security-group.rule.delete
iaas.security-group.rule.list
nlb.loadbalancer.create
nlb.loadbalancer.delete
nlb.loadbalancer.get
nlb.loadbalancer.list
nlb.targetpool.replace

This list covers StackitCluster and StackitMachine reconciliation, including the optional provider-managed bastion host. It does not include permissions for manually creating networks, SSH keys, images, or other prerequisite resources. It also does not include permissions for the in-cluster cloud-provider-stackit add-on if you configure that add-on to manage Kubernetes Service load balancers beyond the provider-managed API server load balancer.

Create a strict role and service account with OpenTofu

Use OpenTofu and the STACKIT provider to create the custom role, service account, role assignment, and service-account key as one managed setup.

The bootstrap identity used by OpenTofu needs these setup permissions:

  • iam.role.add to create the custom role
  • iam.role.get and iam.role.list to read role state
  • iam.member.add to assign the role to the service account
  • iam.member.get to read role-assignment state
  • iam.service-account.create to create the service account
  • iam.service-account.get and iam.service-account.list to read service-account state
  • iam.service-account-key.create to create the service-account key

If the same OpenTofu configuration should also destroy the setup later, the bootstrap identity also needs the corresponding remove/delete permissions: iam.role.remove, iam.service-account.delete, and iam.service-account-key.delete.

Create a STACKIT role:

resource "stackit_authorization_project_custom_role" "cluster_api_provider_stackit" {
  resource_id = var.project_id
  name        = var.role_name
  description = "Least-privilege role for cluster-api-provider-stackit VM, bastion, and API-server load-balancer reconciliation."

  permissions = [
    "iaas.network.get",
    "iaas.public-ip.create",
    "iaas.public-ip.delete",
    "iaas.public-ip.get",
    "iaas.public-ip.list",
    "iaas.server.create",
    "iaas.server.delete",
    "iaas.server.get",
    "iaas.server.list",
    "iaas.server.nic.list",
    "iaas.server.public-ip.add",
    "iaas.server.public-ip.remove",
    "iaas.server.security-group.add",
    "iaas.server.security-group.remove",
    "iaas.security-group.create",
    "iaas.security-group.delete",
    "iaas.security-group.list",
    "iaas.security-group.get",
    "iaas.security-group.rule.create",
    "iaas.security-group.rule.delete",
    "iaas.security-group.rule.list",
    "iaas.security-group.rule.get",
    "nlb.loadbalancer.create",
    "nlb.loadbalancer.delete",
    "nlb.loadbalancer.get",
    "nlb.loadbalancer.list",
    "nlb.targetpool.replace",
  ]
}

Create a STACKIT service account, assign the role and create a service account key:

resource "stackit_service_account" "cluster_api_provider_stackit" {
  project_id = var.project_id
  name       = var.service_account_name
}

resource "stackit_authorization_project_role_assignment" "cluster_api_provider_stackit" {
  resource_id = var.project_id
  role        = stackit_authorization_project_custom_role.cluster_api_provider_stackit.name
  subject     = stackit_service_account.cluster_api_provider_stackit.email
}

resource "stackit_service_account_key" "cluster_api_provider_stackit" {
  project_id            = var.project_id
  service_account_email = stackit_service_account.cluster_api_provider_stackit.email

  depends_on = [
    stackit_authorization_project_role_assignment.cluster_api_provider_stackit,
  ]
}

You will find a working example in hack/tf/iam-setup.

To apply it:

tofu init

tofu apply \
  -var "project_id=${STACKIT_PROJECT_ID}" \
  -var "bootstrap_service_account_key_path=${BOOTSTRAP_SERVICE_ACCOUNT_KEY_PATH}"

Write the generated key to a local file:

export STACKIT_SERVICE_ACCOUNT_JSON_FILE=./.stackit/cluster-api-provider-stackit-serviceaccount.json

mkdir -p "$(dirname "${STACKIT_SERVICE_ACCOUNT_JSON_FILE}")"
tofu output -raw service_account_key_json > "${STACKIT_SERVICE_ACCOUNT_JSON_FILE}"

Next, create the Kubernetes Secret used by StackitCluster:

kubectl create secret generic stackit-credentials \
  --namespace default \
  --from-literal=project-id="${STACKIT_PROJECT_ID}" \
  --from-file=serviceaccount.json="${STACKIT_SERVICE_ACCOUNT_JSON_FILE}"

For Developers

Verify the strict role with the billable e2e tests, not only by reading the permission list. The create/delete scenario exercises VM creation, VM lookup, VM deletion, load balancer creation, target-pool updates, and load balancer cleanup.

Run at least:

export STACKIT_E2E_CREATE_CLUSTER=true
export STACKIT_E2E_NODE_REF=true
export STACKIT_CREDENTIALS_SECRET_NAME=stackit-credentials
export STACKIT_CREDENTIALS_SECRET_NAMESPACE=default

make test-e2e-workload-noderef

To verify the optional bastion path, first import the SSH key pair with the same service account stored in stackit-credentials; key pairs imported with a different service account are not visible to the provider. Then set STACKIT_SSH_KEY_NAME and STACKIT_BASTION_SSH_KEY_NAME and run:

export STACKIT_E2E_CREATE_CLUSTER=true
export STACKIT_E2E_BASTION=true
export STACKIT_SSH_KEY_NAME=<provider-service-account-keypair-name>
export STACKIT_BASTION_SSH_KEY_NAME="${STACKIT_SSH_KEY_NAME}"

make test-e2e-workload-bastion

For release validation, also run the scale, worker-upgrade, control-plane upgrade, and topology e2e targets with the same strict service account:

make test-e2e-workload-scale
make test-e2e-workload-upgrade-workers
make test-e2e-workload-upgrade-control-plane
make test-e2e-workload-topology

[!CAUTION]
Some broader SDK integration tests call helper APIs that are not used by the provider at runtime. For example, TestSDKClientListNetworksIntegration calls ListNetworks and therefore needs iaas.network.list; the provider reconciler only calls GetNetwork with the configured network ID, so iaas.network.get is sufficient for runtime.