Declarative configuration import for Istio gateways, clients, policies, and APIs

For ACP deployment agents can import and declaratively configure gateways, client applications, policies, and APIs. Learn how to utilize the built-in importJob to speed up your workflow and provide access control for your APIs.

About configuration import

In addition to ACP’s import APIs, it is possible to configure your ACP deployment declaratively using the built-in importJob. The configuration import can be used for all ACP deployment types including the ACP SaaS deployment or ACP deployment on kubernetes.

You can define client applications, gateways (both for multi-tenant and single-tenant authorizers), policies, and APIs.

Declarative configuration import allows you to use the GitOps approach where you store your configuration inside your Git repository and have your configuration and infrastructure as code. In this case, your Git repository acts as a single source of truth for your gateways and services configuration. This leads to increased productivity for your teams, enhanced developer experience, improved stability and reliability, consistency, and many more.

You can create multiple import jobs with different responsibilities for each job. You can, for example, prepare one job that creates an Istio Authorizer with its client application, and multiple different import jobs that are responsible for your services/APIs import and their protection.

Any integration with the DevSecOps pipelines is made easier with the declarative configuration import. As with this approach, the import job can be executed in a satellite environment that communicates with ACP using the import API protected by the System tenant credentials.

importJob configuration example

In the example below, you can see the importJob definition that is responsible for importing an Istio Authorizer to a default tenant and a client application for this authorizer in the system workspace for the default tenant.

apiVersion: batch/v1
kind: Job
metadata:
  name: acp-import-job
  namespace: acp-system
spec:
  backoffLimit: 4
  template:
    spec:
      imagePullSecrets:
        - name: docker.cloudentity.io
      volumes:
        - name: import
          configMap:
            name: acp-import
      containers:
        - name: acp-import
          image: "docker.cloudentity.io/acp:{version}"
          imagePullPolicy: IfNotPresent
          args:
            - import
            - --client-id
            - "$(CLIENT_ID)"
            - --client-secret
            - "$(CLIENT_SECRET)"
            - --issuer-url
            - "$(ISSUER_URL)"
            - --mode
            - update
            - --format
            - yaml
            - --input
            - /import/seed.yaml
          env:
          - name: CLIENT_ID
            valueFrom:
              secretKeyRef:
                name: import-job-secrets
                key: IMPORT_JOB_CLIENT_ID
          - name: CLIENT_SECRET
            valueFrom:
              secretKeyRef:
                name: import-job-secrets
                key: IMPORT_JOB_CLIENT_SECRET
          - name: ISSUER_URL
            valueFrom:
              secretKeyRef:
                name: import-job-secrets
                key: ISSUER_URL
          volumeMounts:
            - mountPath: /import
              name: import
              readOnly: true
      restartPolicy: Never
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: acp-import
  namespace: acp-system
data:
  seed.yaml: |
    clients:
      - tenant_id: {tid}
        authorization_server_id: {aid}
        client_id: {client_id}
        client_secret: {client_secret}
        client_name: sample gateway
        token_endpoint_auth_method: client_secret_basic
        grant_types:
          - client_credentials
        scopes:
          - read_gateway_configuration
          - write_gateway_configuration
          - push_gateway_requests
          - introspect_tokens
    gateways:
      - tenant_id: {tid}
        authorization_server_id: {aid}
        id: local_istio_gateway
        name: Sample Istio Gateway
        type: istio
        client_id: {client_id}    

Prerequisites

  1. Access to the ACP tenant and its System workspace.

    System workspace access

    The importJob results in a call to ACP’s import configuration API that is protected by System workspace credentials. System workspace access, by default, is hidden behind a feature flag. If you need access to the System workspace for your tenant, contact Cloudentity Sales team.

  2. Client application created in the System workspace.

    Client configuration

    To be able to import the defined configuration, your import job needs to be able to authenticate to ACP by calling the ACP OAuth 2.0 token endpoint. Therefore, you need to allow the client credentials OAuth grant flow for your client application and assign the manage_configuration scope to it. To learn more about creating clients used for calling ACP’s APIs, see the Getting started with ACP REST API

  3. kubernetes cluster up and running.

    For the purpose of this article, the acp-on-k8s environment is used that allows you to rapidly stand up your ACP instance in your local development environment. For prerequisites and references for this deployment, visit a dedicated acp-on-k8s GitHub repository.

  4. Istio Gateway deployed in your kubernetes cluster.

  5. Service of your choice deployed in your kubernetes cluster.

    You need to deploy a service in order to be able to import APIs to your ACP instance and apply access control to protect them.

    Tip

    If you do not have a deployment-ready service at hand, you can deploy, for example, a httpbin service which can be found in the acp-on-k8s GitHub repository.

Configuration files

This article is based on custom deployment configuration files. For default files and more examples, check the acp-on-k8s GitHub repository.

Configure Istio Authorizer

  1. In a directory of your choice, create a kustomization.yaml file.

    As kubectl supports managing objects using Kustomize, it is possible to generate kubernetes Secrets and ConfigMaps. In this case, the kustomization.yaml file is responsible for storing the secrets and other data crucial for configuring your Istio Authorizer.

    Example:

     resources:
     - register-gateway.yaml
     - manifest.yaml
     secretGenerator:
     - name: istio-authorizer-secrets
       namespace: acp-system
       literals:
       - CLIENT_ID={your_client_id}
       - CLIENT_SECRET={your_client_secret}
       - ISSUER_URL=https://{tid}.us.authz.cloudentity.io/{tid}/system
     - name: import-job-secrets
       namespace: acp-system
       literals:
       - ISSUER_URL=https://{tid}.us.authz.cloudentity.io/{tid}/system
       - IMPORT_JOB_CLIENT_ID={IMPORT_JOB_CLIENT_ID}
       - IMPORT_JOB_CLIENT_SECRET={IMPORT_JOB_CLIENT_SECRET}
    

    The resources objects point to files that define your Istio Authorizer configuration. You will create both files in the next steps.

    With the kubernetes secretGenerator you can create kubernetes Secrets, in this case, istio_authorizer_secrets and import_job_secrets that can store literals.

    For the istio-authorizer-secrets object define the following literals:

    • CLIENT_ID which value should be set for your Istio Authorizer client identifier.

    • CLIENT_SECRET which value should be set for your Istio Authorizer client secret.

    • ISSUER_URL which value must point to the issuer URL of the System workspace of your tenant.

    Issuer URL

    Your issuer URL always follow the format: https://{tid}.us.authz.cloudentity.io/{tid}/system where {tid} stands for your tenant identifier.

    For the import-job-secrets Secret define the following literals:

    • IMPORT_JOB_CLIENT_ID value must point to the client identifier of the client application that you have created in the System workspace for your tenant.

    • IMPORT_JOB_CLIENT_SECRET value must point to the client secret of the client application that you have created in the System workspace for your tenant.

    • ISSUER_URL which value must point to the issuer URL of the System workspace of your tenant.

    Issuer URL

    Your issuer URL always follow the format: https://{tid}.us.authz.cloudentity.io/{tid}/system where {tid} stands for your tenant identifier.

    Both the IMPORT_JOB_CLIENT_SECRET and the IMPORT_JOB_CLIENT_ID parameter are passed to the ACP OAuth 2.0 token endpoint in the client credentials grant flow to authenticate the client application that requests your configuration import for your authorizer.

    Security recommendation

    To increase security of secrets stored in your repository, it is recommended to encrypt your kubernetes secrets. You can use tools like Mozilla SOPS or Bitnami Sealed Secrets to encrypt your secrets.

    When the secrets are applied to your kubernetes deployment, the secrets are encrypted and visible as plain text. Anyone who is authorized to create a Pod in a namespace can read any secret in that namespace; this includes indirect access such as the ability to create a Deployment. To mitigate the risks, kubernetes recommends to:

    • Enable encryption at Rest for secrets.

    • Enable or configure RBAC rules that restrict reading data in secrets.

    • Where appropriate, use mechanisms such as RBAC to limit which principals are allowed to create or replace secrets.

    To learn more, visit kubernetes secrets documentation

  2. In a directory of your choice, create a manifest.yaml file and provide a definition for your Istio Authorizer instance. See an example below.

    apiVersion: v1
    kind: Namespace
    metadata:
      name: acp-system
    ---
    apiVersion: v1
    kind: ServiceAccount
    imagePullSecrets:
    - name: docker.cloudentity.io
    metadata:
      name: istio-authorizer
      namespace: acp-system
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: istio-authorizer
      namespace: acp-system
    roleRef:
      apiGroup: ""
      kind: ClusterRole
      name: istio-authorizer
    subjects:
    - kind: ServiceAccount
      name: istio-authorizer
      namespace: acp-system
    ---
    apiVersion: rbac.authorization.k8s.io/v1beta1
    kind: ClusterRoleBinding
    metadata:
      name: istio-authorizer-auth-delegator
      namespace: default
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: system:auth-delegator
    subjects:
    - kind: ServiceAccount
      name: istio-authorizer
      namespace: acp-system
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: istio-authorizer
      namespace: acp-system
    rules:
    - apiGroups:
      - "apps"
      resources:
      - "deployments"
      verbs:
      - "list"
    ---
    apiVersion: v1
    kind: ConfigMap
    metadata:
        name: istio-authorizer-data
        namespace: acp-system
    data:
      ca.pem: |
        -----BEGIN CERTIFICATE-----
        MIIDfjCCAmagAwIBAgIUHOLlcMhX8uJyFafYYNXYBMBJr2swDQYJKoZIhvcNAQEL
        BQAwVzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
        B1NlYXR0bGUxFDASBgNVBAoTC0Nsb3VkZW50aXR5MQswCQYDVQQLEwJDQTAeFw0y
        MTAxMjAwOTQ4MDBaFw0yNjAxMTkwOTQ4MDBaMFcxCzAJBgNVBAYTAlVTMRMwEQYD
        VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdTZWF0dGxlMRQwEgYDVQQKEwtDbG91
        ZGVudGl0eTELMAkGA1UECxMCQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
        AoIBAQDKJroMoumr9jY129z7uf0WrvMxmzexP72ogINlQlM/p910YfnLYSWOUEIH
        kQ5eyq3ATuesPzamNxjq4JkafopsMBeieCeVzir4VTmsxLxbBZG2GjdsGpyXmrXb
        LXTb5dgNcolYh6LLPb11cBeb9TAy2D97Vx4t1Hr2SeLG1VvkNyNnoog6tZdmJUis
        ufW9GOyXgiAv46rtgvvpzYn+LbE7oiXlQHICHNpeTh7140HG7eWMPubINuilGZBY
        W80IGTpGUf2Vmuwo2LA17z5/3IB0dqlY5eT321TYVoqH1TYZrMllSLc+2x750e+l
        E9sHil8QPptAS74UWUsq2PgIvyZbAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP
        BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTKWQeEZBRJ/UO1Py7EfTaOqVujbTAN
        BgkqhkiG9w0BAQsFAAOCAQEAsg5ker9FPcq1u5E+y4Qq1yjUeuOT0kap+aIE1mp2
        LSQoEA+tb40s/iNmTMdvCGReeoRoVYKz66+3zGoFYg5W1c5Ct9whjiLhKP1Pzc4I
        JbPGklSrnnAwD72ypLF4yrTMTD65gTMsr2ao0MOe6vy/Z8R2uz48QJHhhi71VGhi
        FstSiWvb4AgNhN39Ag5ufLtrGCbuZw5TSeW0J7PTBoYV1Z/0jrsdqxk8MjnbR8Qe
        VyZGyIRnGkXUtC239/Lz0v0PXVwAPUF8ITb0JYcG/ojIc5VfTOkQOeOHHBaS+dXt
        HBJjBbnwpN4tNFhczJkbga16hL+vV0I4z61mJG8OkDr25Q==
        -----END CERTIFICATE-----    
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      labels:
        app: istio-authorizer
      namespace: acp-system
      name: istio-authorizer
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: istio-authorizer
      template:
        metadata:
          labels:
            app: istio-authorizer
          name: istio-authorizer
        spec:
          serviceAccountName: istio-authorizer
          containers:
            - image: docker.cloudentity.io/istio-authorizer:latest
              imagePullPolicy: IfNotPresent
              name: istio-authorizer
              args:
              - --client-id
              - "$(CLIENT_ID)"
              - --client-secret
              - "$(CLIENT_SECRET)"
              - --issuer-url
              - "$(ISSUER_URL)"
              - --disable-service-discovery
              - "true"
              - --root-ca
              - /data/ca.pem
              env:
              - name: LOGGING_LEVEL
                value: "INFO"
              - name: CLIENT_ID
                valueFrom:
                  secretKeyRef:
                    name: istio-authorizer-secrets
                    key: CLIENT_ID
              - name: CLIENT_SECRET
                valueFrom:
                  secretKeyRef:
                    name: istio-authorizer-secrets
                    key: CLIENT_SECRET
              - name: ISSUER_URL
                valueFrom:
                  secretKeyRef:
                    name: istio-authorizer-secrets
                    key: ISSUER_URL
              volumeMounts:
              - mountPath: /data
                name: data
              ports:
              - containerPort: 9001
              readinessProbe:
                tcpSocket:
                  port: 9001
                initialDelaySeconds: 5
                periodSeconds: 10
              livenessProbe:
                tcpSocket:
                  port: 9001
                initialDelaySeconds: 15
                periodSeconds: 20
          volumes:
            - name: data
              configMap:
                name: istio-authorizer-data
    ---
    apiVersion: v1
      kind: Service
      metadata:
        name: istio-authorizer
        namespace: acp-system
        labels:
          app: istio-authorizer
    spec:
      ports:
        - port: 9001
          name: grpc
        - port: 9002
          name: http
      selector:
        app: istio-authorizer
    

    The example above is responsible for the following steps in your deployment:

    1. It defines the namespace (acp-system) where your Istio Authorizer is to be deployed.

    2. It defines a kubernetes service account for your pod with the authorizer.

    3. It provides three kubernetes cluster role bindings that are responsible for granting permissions across your whole kubernetes cluster.

    4. It defines a cluster role for your kubernetes cluster. It is used to grant access to secrets in your acp-system namespace.

    5. It provides a ConfigMap for your Istio Authorizer that contains the certificate of your authorizer used for TLS authentication.

    6. It provides deployment configuration for your Istio Authorizer instance.

      In the deployment configuration, you can see that the istio-authorizer specification contains the latest Istio Authorizers docker image. It also provides the following deployment arguments:

      • --client-id which value points to the CLIENT_ID environment variable that stores the client identifier of the Istio Authorizer’s client which you have provided in the kustomization.yaml file.

      • --client-secret which value points to the CLIENT_SECRET environment variable that stores the client secret of the Istio Authorizer’s client you have provided in the kustomization.yaml file.

      • --issuer-url which value points to the ISSUER_URL environment variable that stores the issuer URL of the Istio Authorizer’s client you have provided in the kustomization.yaml file.

      • --disable-service-discovery flag set to true. If enabled, the automatic service discovery is disabled for your Istio Authorizer instance. This causes that your services and APIs are not automatically imported to ACP and you can declaratively import them using the importJob.

      • --root-ca which stores the path to the TLS certificate for your Istio Authorizer.

      In the deployment configuration, environment variables are also defined for your Istio Authorizer’s kubernetes deployment. The variables are used, for example, to define the values for the arguments listed above.

      volumeMounts are a part of the deployment configuration that is responsible for mounting of the declared volume into a container within the same pod. In the code snippet above, you can see that a data volume is defined and also mounted using the volumeMounts so that it exists within the same pod as the Istio Authorizer’s deployment.

    7. The manifest.yaml file also provides a definition for the Istio Authorizer’s service and ports mapping that are used to enable either the gRPC or the HTTP services deployment and protection.

Define import job and gateway configuration

In a directory of your choice, create a register-gateway.yaml file that is to be responsible for providing configuration for your import job and for providing actual ACP configuration to be imported.

Configure import job

The first part of the register-gateway.yaml file, should be the definition of your import job. See an example below:

apiVersion: batch/v1
kind: Job
metadata:
  name: acp-import-job
  namespace: acp-system
spec:
  backoffLimit: 4
  template:
    spec:
      imagePullSecrets:
        - name: docker.cloudentity.io
      volumes:
        - name: import
          configMap:
            name: acp-import
      containers:
        - name: acp-import
          image: "docker.cloudentity.io/acp:1.14.1"
          imagePullPolicy: IfNotPresent
          args:
            - import
            - --client-id
            - "$(CLIENT_ID)"
            - --client-secret
            - "$(CLIENT_SECRET)"
            - --issuer-url
            - "$(ISSUER_URL)"
            - --mode
            - update
            - --format
            - yaml
            - --input
            - /import/seed.yaml
          env:
          - name: CLIENT_ID
            valueFrom:
              secretKeyRef:
                name: import-job-secrets
                key: IMPORT_JOB_CLIENT_ID
          - name: CLIENT_SECRET
            valueFrom:
              secretKeyRef:
                name: import-job-secrets
                key: IMPORT_JOB_CLIENT_SECRET
          - name: ISSUER_URL
            valueFrom:
              secretKeyRef:
                name: import-job-secrets
                key: ISSUER_URL
          volumeMounts:
            - mountPath: /import
              name: import
              readOnly: true
      restartPolicy: Never
---

The acp-import-job is a kubernetes job that is responsible for creating a pod and retrying the execution of the import job pod until it successfully terminates. The backoffLimit is set to 4 so after four import failures, your pod my reach the CrashLoopBackoff limit error. This may mean that you have an error in the configuration that you are trying to import.

As with the manifest.yaml file, you can see that for your job definition the same arguments and environment variables are set. Additionally, you can see that the --mode argument set to update is used. This argument is responsible for defining what happens in a case when there are any configuration conflicts when you are trying to import your configuration. To know more about the --mode argument and possible values, see the insert mode documentation.

The job definition also contains the --format argument which, contrary to a standard API call to ACP’s import/export tenant configuration APIs, accepts YAML input for your configuration. Additionally, the --input argument is added which is responsible for providing a path to your imported configuration that residues on your kubernetes pod.

Provide configuration for gateway and its client

The second part of the register-gateway.yaml file should provide a ConfigMap for your imported data. See an example below:

apiVersion: v1
kind: ConfigMap
metadata:
  name: acp-import
  namespace: acp-system
data:
  seed.yaml: |
    clients:
      - tenant_id: {tid}
        authorization_server_id: system
        client_id: {your_gateway_client_id}
        client_secret: {client_secret}
        client_name: My Local Gateway
        token_endpoint_auth_method: client_secret_basic
        grant_types:
          - client_credentials
        scopes:
          - read_gateway_configuration
          - write_gateway_configuration
          - push_gateway_requests
          - introspect_tokens
    gateways:
      - tenant_id: {tid}
        authorization_server_id: {aid}
        id: {gateway_id}
        name: Sample Istio Gateway import
        type: istio
        client_id: {your_gateway_client_id}    

The above example imports an Istio Gateway (Authorizer) to a default workspace of a default tenant. The configuration of the gateway also contains a reference to the client_id ({your_gateway_client_id}) of the client application that is also imported.

Gateway object

In ACP, a gateway object consists of two entities: actual gateway (authorizer) and a client application created for that gateway (authorizer) in the System workspace of the tenant where the gateway is created. In ACP application, whenever you create a gateway in your tenant, a client application is automatically created for you in your tenant’s System workspace. This is why, while importing a gateway, you must also import a client application for your gateway.

For client applications that are created for authorizers, the following scopes must be assigned: introspect_tokens, push_gateway_requests, read_gateway_configuration, and write_gateway_configuration.

Deploy Istio Authorizer

After defining your Istio Authorizers deployment configuration, you need to deploy it to your cluster. Depending on your deployment configuration, you can do it, for example, in the following ways:

  • using the kubectl apply command, for example:

    • kubectl apply -f {FILENAME} to apply a single file where {FILENAME} is the file you want to apply.

    • kubectl apply -k {DIRECTORY} to apply a directory with all the files it contains. Replace the {DIRECTORY} variable with a directory where you have all your configuration files that you have prepared so far.

  • using the kubectl kustomize command, for example kubectl kustomize {DIRECTORY} where the {DIRECTORY} is a variable that references the directory where you have your kustomization.yaml file and other resources.

By running kubectl get pods -A you are able to get all pods from all namespaces running on your kubernetes cluster. The list should contain two pods similar to the following:

$ kubectl get pods -A
NAMESPACE            NAME                                        READY   STATUS      RESTARTS   AGE
acp-system           acp-import-job-frpj6                        0/1     Completed   0          48m
acp-system           istio-authorizer-69fbdddf48-8ngjq           1/1     Running     1          48m

You can see that the list contains two pods running in the acp-system namespace, one being the acp-import-job-xyz responsible for the import job, and the second responsible for your Istio Authorizer’s deployment (istio-authorizer-xyz). You can see the logs of your authorizer by executing the following kubectl logs $pod -n $namespace. If you were to run this command for the pods from the codeblock above (kubectl logs istio-authorizer-69fbdddf48-8ngjq -n acp-system) you would see the output similar to the following:

{"level":"info","msg":"Starting authorizer reload...","time":"2021-10-22T09:30:11Z"}
{"level":"info","msg":"Authorization configuration reloaded (948.300757ms)","time":"2021-10-22T09:30:12Z"}

If you have the service discovery enabled (--disable-service-discovery set to false in your manifest.yaml file), at this point, you should be able to see your APIs and services in your ACP tenant.

Quick summary

Let’s quickly recap what you have achieved so far:

  1. You have prepared your Istio Authorizer’s deployment configuration that consists of three files:

    • kustomization.yaml that defines the resources for your Istio Authorizer’s deployment and is responsible for generating kubernetes Secrets.

    • manifest.yaml that provides configuration for your Istio Authorizer’s deployment.

    • register-gateway.yaml that is responsible for providing the import kubernetes job and data that is going to be imported to your ACP tenant.

  2. You have deployed your configuration files.

In your ACP tenant, in a workspace that you have specified in the register-gateway.yaml file, you should see your Istio Authorizer that you have imported. Go to your workspace > APIs > Gateways if you wish to confirm it.

In the System workspace for your ACP tenant, you should be able to see a client application for your Istio Authorizer. It has the same client identifier as the Istio Authorizer instance that you have imported. Go to System workspace > Applications if you wish to confirm it.

In general, you have done the groundwork that allows you to protect your APIs. Once you have your Istio Authorizer integrated with ACP, you can proceed to the next steps. You will prepare a deployment of your import job that is to be responsible for importing policies, services, APIs, and assigning your authorization policies to those imported APIs. This way, you can have your policies, services, and APIs definition completely separate from your Istio Authorizer’s deployment.

Configure import job for policies, services, and APIs

To be able to declaratively configure your policies, services, and APIs, you need to perform the following steps:

  1. Connect an Istio Authorization policy to your Istio Gateway.

    Istio allows externalized authorization decisioning. In order to get your Istio Authorizer integrated with an Istio Gateway, you need to create an [Istio Authorization policy] that delegates the authorization decisioning and access control to Istio Authorizer and ACP.

  2. Prepare a kubernetes kustomization.yaml file that is responsible for generating the secrets for your import job and providing resources files for your deployment.

  3. Prepare your import job deployment and the imported configuration.

    Your import job is going to be executed in a separate pod. You can create multiple different import jobs that are responsible for importing different sets of policies, services, and APIs.

Define kustomization

In a directory of your choice prepare a kustomization.yaml file that is responsible for generating the secrets for your import job and providing resources files for your deployment. See example below:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- protect-apis.yaml
- istio-policy.yaml
secretGenerator:
- name: import-job-secrets
  namespace: acp-system
  literals:
  - ISSUER_URL=https://<tenantid>.us.authz.cloudentity.io/<tenantid>/system
  - IMPORT_JOB_CLIENT_ID={IMPORT_JOB_CLIENT_ID}
  - IMPORT_JOB_CLIENT_SECRET={IMPORT_JOB_CLIENT_SECRET}

The first part of your kustomization.yaml file is responsible for providing a resources object that points to files that define your deployment configuration. You will create those files in the next steps.

With the kubernetes secretGenerator you can create kubernetes Secrets, in this case, import_job_secrets that can store literals for your deployment.

For the import-job-secrets Secret define the following literals:

  • ISSUER_URL which value must point to the issuer URL of the System workspace of your tenant.

Issuer URL

Your issuer URL always follow the format: https://{tid}.us.authz.cloudentity.io/{tid}/system where {tid} stands for your tenant identifier.

  • IMPORT_JOB_CLIENT_ID value must point to the client identifier of the client application that you have created in the System workspace for your tenant.

  • IMPORT_JOB_CLIENT_SECRET value must point to the client secret of the client application that you have created in the System workspace for your tenant.

Both the IMPORT_JOB_CLIENT_SECRET and the IMPORT_JOB_CLIENT_ID parameter are used as the part of the client credentials grant flow call to the ACP OAuth 2.0 token endpoint to authenticate the client application that requests your policies, services, and APIs import for your tenant.

Security recommendation

To increase security of secrets stored in your repository, it is recommended to encrypt your kubernetes secrets. You can use tools like Mozilla SOPS or Bitnami Sealed Secrets to encrypt your secrets.

When the secrets are applied to your kubernetes deployment, the secrets are encrypted and visible as plain text. Anyone who is authorized to create a Pod in a namespace can read any secret in that namespace; this includes indirect access such as the ability to create a Deployment. To mitigate the risks, kubernetes recommends to:

  • Enable encryption at Rest for secrets.

  • Enable or configure RBAC rules that restrict reading data in secrets.

  • Where appropriate, use mechanisms such as RBAC to limit which principals are allowed to create or replace secrets.

To learn more, visit kubernetes secrets documentation

Prepare Istio authorization policy

In a directory of your choice, prepare an istio-policy.yaml file. See example below.

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: acp-authorizer
  namespace: default
spec:
  action: CUSTOM
  provider:
    name: acp-authorizer
  rules:
  - {}

The above Istio authorization policy is responsible for creating a CUSTOM access control action for APIs deployed behind the Istio Gateway. The CUSTOM action delegates the authorization to an external provider, in this case, ACP.

Note

In the example above, the policy is applied to all services in the default namespace. Take a look at Authorization Policy Istio documentation if you would like to protect only a specific service.

Configure import job

In a directory of your choice, create a protect-apis.yaml file that both configures your import job, and provides the actual ACP configuration to be imported.

The first part of the register-gateway.yaml file, should be the definition of your import job. See an example below:

apiVersion: batch/v1
kind: Job
metadata:
  name: acp-import-job
  namespace: acp-system
spec:
  backoffLimit: 4
  template:
    spec:
      imagePullSecrets:
        - name: docker.cloudentity.io
      volumes:
        - name: import
          configMap:
            name: acp-import
      containers:
        - name: acp-import
          image: "docker.cloudentity.io/acp:1.14.1"
          imagePullPolicy: IfNotPresent
          args:
            - import
            - --client-id
            - "$(CLIENT_ID)"
            - --client-secret
            - "$(CLIENT_SECRET)"
            - --issuer-url
            - "$(ISSUER_URL)"
            - --mode
            - update
            - --format
            - yaml
            - --input
            - /import/seed.yaml
          env:
          - name: CLIENT_ID
            valueFrom:
              secretKeyRef:
                name: import-job-secrets
                key: IMPORT_JOB_CLIENT_ID
          - name: CLIENT_SECRET
            valueFrom:
              secretKeyRef:
                name: import-job-secrets
                key: IMPORT_JOB_CLIENT_SECRET
          - name: ISSUER_URL
            valueFrom:
              secretKeyRef:
                name: import-job-secrets
                key: ISSUER_URL
          volumeMounts:
            - mountPath: /import
              name: import
              readOnly: true
      restartPolicy: Never
---

The acp-import-job is a kubernetes job that is responsible for creating a pod and retrying the execution of the import job pod until it successfully terminates. The backoffLimit is set to 4 so after four import failures, your pod my reach the CrashLoopBackoff limit error. This may mean that you have an error in the configuration that you are trying to import.

Your import job also contains the following arguments and environment variables:

  • --client-id which value points to the CLIENT_ID environment variable that stores the client identifier of the client application which you have provided in the kustomization.yaml file.

  • --client-secret which value points to the CLIENT_SECRET environment variable that stores the client secret of the client application you have provided in the kustomization.yaml file.

  • --issuer-url which value points to the ISSUER_URL environment variable that stores the issuer URL of the System workspace where you have created the client application.

  • --mode argument set to update.

    Insert mode

    This argument is responsible for defining what happens in a case when there are any configuration conflicts when you are trying to import your configuration. To know more about the --mode argument and possible values, see the insert mode documentation.

  • --format that defines the format of your configuration import.

    Import configuration format

    The import job, contrary to a standard API call to ACP’s import/export tenant configuration APIs, accepts YAML input for your configuration.

  • --input argument is added which is responsible for providing a path to your imported configuration that residues on your kubernetes pod.

Additionally, your import job definition also contains the environment variables that are responsible for extracting your confidential data (like, for example, client identifiers) from kubernetes Secrets and passing their values to arguments described above.

Provide configuration input

Once the import job is defined, you can proceed to adding your configuration input that is going to be imported to your ACP tenant.

Add your configuration input (ConfigMap) to the protect-apis.yaml file. See example below.

Tip

If you want, you can add your configuration input to a separate file. If you do so, remember that you need to add your file to the resources object in the kustomization.yaml file.

apiVersion: v1
kind: ConfigMap
metadata:
  name: acp-import
  namespace: acp-system
data:
  seed.yaml: |
    policies:
      - tenant_id: {tid}
        server_id: {aid}
        id: block_test_policy
        policy_name: block_test
        language: cloudentity
        type: api
        validators:
          - name: "false"
      - tenant_id: {tid}
        server_id: {aid}
        id: allow_test_policy
        policy_name: allow_test
        language: cloudentity
        type: api
        validators:
          - name: "true"
    gateway_api_groups:
      - tenant_id: {tid}
        server_id: {aid}
        service_id: httpbin
        gateway_id: sample-istio-authorizer
        name: "default/httpbin"
        id: {{ encSpiffeID "spiffe://cluster.local/ns/default/sa/httpbin" }}
        apis:
          - method: GET
            path: /deny
          - method: GET
            path: /anything
    services:
      - id: httpbin
        tenant_id: {tid}
        authorization_server_id: {aid}
        gateway_id: sample-istio-authorizer
        name: default/httpbin
        apis:
          - method: GET
            path: /deny
            can_have_policy: true
            policy_id: block_test_policy
          - method: GET
            path: /anything
            can_have_policy: true
            policy_id: allow_test_policy    

The imported configuration contains:

  • Cloudentity authorization policies for APIs:

    • block_test_policy that is responsible for blocking all incoming API requests.

    • allow_test_policy that accepts all incoming API requests.

  • httpbin API group for your sample Istio Authorizer

    API group IDs for Istio Authorizer

    For Istio Authorizer, the API group identifier is a Base64 URL encoded SPIFFE ID which is built being based on the service account of the protected pod. To avoid using long and unreadable API groups identifiers, ACP supports Base64 encoding of SPIFFE IDs. You can define your API group identifier by following the format:

    - id: "{{ encServiceID \"spiffe://{domain}/ns/{namespace}/sa/{service_id}" }}"
    
  • httpbin service for your sample Istio Authorizer

You can see that your configuration input contains very similar definitions of API groups and services. When you deploy an authorizer with service discovery enabled, it discovers API groups that you can later on assign to services. You can omit importing API groups and import just a service with APIs, but in this case, your APIs are not visible in the ACP’s UI under your gateway.

Deploy configuration changes

After defining your import job deployment configuration and the configuration that you want to import to ACP, you need to deploy it to your cluster. Depending on your deployment configuration, you can do it, for example, in the following ways:

  • using the kubectl apply command, for example:

    • kubectl apply -f {FILENAME} to apply a single file where {FILENAME} is the file you want to apply.

    • kubectl apply -k {DIRECTORY} to apply a directory with all the files it contains. Replace the {DIRECTORY} variable with a directory where you have all your configuration files that you have prepared so far.

  • using the kubectl kustomize command, for example kubectl kustomize {DIRECTORY} where the {DIRECTORY} is a variable that references the directory where you have your kustomization.yaml file and other resources.

By running kubectl get pods -A you are able to get all pods from all namespaces running on your kubernetes cluster. The list should contain should include your new import job:

$ kubectl get pods -A
NAMESPACE            NAME                                        READY   STATUS      RESTARTS   AGE
acp-system           httpbin-protect-apis-job-8hqbm              0/1     Completed   0          3m

Deployment result

Once your configuration gets deployed to your kubernetes cluster, the following events take place:

  1. Your Istio Gateway has an authorization policy assigned that is responsible for delegating access control to ACP.

  2. With the kustomize.yaml file, your kustomization is applied and the Secrets are created.

  3. Your import job takes place and the configuration that you have provided is imported to your ACP tenant.

In the examples above, policies, an httpbin API group, and an httpbin service are imported to ACP. At the same time, the imported APIs are protected with the imported policies, one that allows all API requests, and the second that blocks all incoming API requests.

Check that your APIs are protected

To check if the policies were successfully imported and your APIs are protected, you need to make API requests to the endpoints that you have imported. To test it locally, execute the following commands in your terminal:

kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.9/samples/sleep/sleep.yaml
export SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})

The commands above are responsible for creating a sleep pod in your default namespace. Then, the sleep pod name is exported as an environment variable.

Then, you can enter the sleep pod’s shell by calling the kubectl exec -it $SLEEP_POD sh command and call your protected APIs. For the imported examples from above sections, the request would look like the following: curl http://httpbin.default:80/deny -v. As this API had the block_test_policy applied, the response to the request is a 403 unauthorized access error. Changing the path to /anything would result in a successful request, as the policy assigned to this endpoint allows all requests.