Today in Kubernetes, the exposure of secrets for connecting applications to external services such as REST APIs, databases, event buses, and more is both manual and bespoke. Each service provider suggests a different way to access their secrets and each application developer consumes those secrets in a way that is custom to their applications. While there is a good deal of value to this level of flexibility, large development teams lose overall velocity dealing with each unique solution. To combat this, we already see teams adopting internal patterns for how to achieve this application-to-service linkage.
The goal of this specification is to create a Kubernetes-wide specification for communicating service secrets to applications in an automated way. It aims to create a mechanism that is widely applicable, but without excluding other strategies for systems that it does not fit easily. The benefit of a Kubernetes-wide specification is that all of the actors in an ecosystem can work towards a clearly defined abstraction at the edge of their expertise and depend on other parties to complete the chain.
- Application Developers expect secrets to be exposed in a consistent and predictable way
- Service Providers expect their secrets to be collected and exposed to users in a consistent and predictable way
- Platforms expect to retrieve secrets from Service Providers and expose them to Application Developers in a consistent and predictable way
The pattern of Service Binding has prior art in non-Kubernetes platforms. Heroku pioneered this model with Add-ons and Cloud Foundry adopted similar ideas with their Services. Other open source projects like the Open Service Broker aim to help with this pattern on those non-Kubernetes platforms. In the Kubernetes ecosystem, the CNCF Sandbox Cloud Native Buildpacks project has proposed a buildpack-specific specification exclusively addressing the application developer portion of this pattern.
The Service Binding Specification for Kubernetes project is a community lead effort. A bi-weekly working group call is open to the public. Discussions occur here on GitHub and on the #bindings-discuss channel in the Kubernetes Sack.
Behavior within the project is governed by the Contributor Covenant Code of Conduct.
If you catch an error in the specification’s text, or if you write an implementation, please let us know by opening an issue or pull request at our GitHub repository.
- Service Binding Specification for Kubernetes
- Provisioned Service
- Application Projection
- Service Binding
- Extensions
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" are to be interpreted as described in RFC 2119.
The key words "unspecified", "undefined", and "implementation-defined" are to be interpreted as described in the rationale for the C99 standard.
An implementation is not compliant if it fails to satisfy one or more of the MUST, MUST NOT, REQUIRED, SHALL, or SHALL NOT requirements for the protocols it implements. An implementation is compliant if it satisfies all the MUST, MUST NOT, REQUIRED, SHALL, and SHALL NOT requirements for the protocols it implements.
- Duck Type
- Any type that meets the contract defined in a specification, without being an instance of a specific concrete type. For example, for specification that requires a given key on
status
, any resource that has that key on itsstatus
regardless of itskind
would be considered a duck type of the specification. - Service
- Any software that exposes functionality. Examples include a database, a message broker, an application with REST endpoints, an event stream, an Application Performance Monitor, or a Hardware Security Module.
- Application
- Any process, running within a container. Examples include a Spring Boot application, a NodeJS Express application, or a Ruby Rails application. Note: This is different than an umbrella application as defined by the Kubernetes SIG, which refers to a set of micro-services.
- Service Binding
- The act of or representation of the action of providing information about a Service to an Application
- ConfigMap
- A Kubernetes ConfigMap
- Secret
- A Kubernetes Secret
A Provisioned Service resource MUST define a .status.binding.name
which is a LocalObjectReference
-able to a Secret
. The Secret
MUST be in the same namespace as the resource. The Secret
SHOULD contain a type
entry with a value that identifies the abstract classification of the binding. It is RECOMMENDED that the Secret
also contain a provider
entry with a value that identifies the provider of the binding. The Secret
MAY contain any other entry.
Extensions and implementations MAY define additional mechanisms to consume a Provisioned Service that does not conform to the duck type.
status:
binding:
name: # string
...
status:
...
binding:
name: production-db-secret
Other than the recommended type
and provider
entries, there are no other reserved Secret
entries. In the interests of consistency, if a Secret
includes any of the following entry names, the entry value MUST meet the specified requirements:
Name | Requirements |
---|---|
host |
A DNS-resolvable host name or IP address |
port |
A valid port number |
uri |
A valid URI as defined by RFC3986 |
username |
A string-based username credential |
password |
A string-based password credential |
certificates |
A collection of PEM-encoded X.509 certificates, representing a certificate chain used in mTLS client authentication |
private-key |
A PEM-encoded private key used in mTLS client authentication |
Secret
entries that do not meet these requirements MUST use different entry names.
apiVersion: v1
kind: Secret
metadata:
name: production-db
stringData:
type: mysql
provider: bitnami
host: localhost
port: 3306
username: root
password: root
A Binding Secret
MUST be volume mounted into a container at $SERVICE_BINDING_ROOT/<binding-name>
with directory names matching the name of the binding. Binding names MUST match [a-z0-9\-\.]{1,253}
. The $SERVICE_BINDING_ROOT
environment variable MUST be declared and can point to any valid file system location.
The Secret
MUST contain a type
entry with a value that identifies the abstract classification of the binding. It is RECOMMENDED that the Secret
also contain a provider
entry with a value that identifies the provider of the binding. The Secret
MAY contain any other entry.
The name of a secret entry file name SHOULD match [a-z0-9\-\.]{1,253}
. The contents of a secret entry may be anything representable as bytes on the file system including, but not limited to, a literal string value (e.g. db-password
), a language-specific binary (e.g. a Java KeyStore
with a private key and X.509 certificate), or an indirect pointer to another system for value resolution (e.g. vault://production-database/password
).
The collection of files within the directory MAY change between container launches. The collection of files within the directory SHOULD NOT change during the lifetime of the container.
$SERVICE_BINDING_ROOT
├── account-database
│ ├── type
│ ├── provider
│ ├── uri
│ ├── username
│ └── password
└── transaction-event-stream
├── type
├── connection-count
├── uri
├── certificates
└── private-key
A Service Binding describes the connection between a Provisioned Service and an Application Projection. It is codified as a concrete resource type. Multiple Service Bindings can refer to the same service. Multiple Service Bindings can refer to the same application. An exemplar CRD can be found here.
Restricting service binding to resources within the same namespace is strongly RECOMMENDED. Cross-namespace service binding SHOULD be secured appropriately by the implementor to prevent attacks like privilege escalation and secret enumeration.
A Service Binding resource MUST define a .spec.application
which is an ObjectReference
-like declaration to a PodSpec
-able resource. A ServiceBinding
MAY define the application reference by-name or by-label selector. A name and selector MUST NOT be defined in the same reference. A Service Binding resource MUST define a .spec.service
which is an ObjectReference
-like declaration to a Provisioned Service-able resource.
A Service Binding Resource MAY define a .spec.mappings
which is an array of Mapping
objects. A Mapping
object MUST define name
and value
entries. The value
of a Mapping
MUST be handled as a Go Template exposing binding Secret
keys for substitution. The executed output of the template MUST be added to the Secret
exposed to the resource represented by application
as the key specified by the name
of the Mapping
.
A Service Binding Resource MAY define a .spec.env
which is an array of EnvVar
. An EnvVar
object MUST define name
and key
entries. The key
of an EnvVar
MUST refer to a binding Secret
key name including any key defined by a Mapping
. The value of this Secret
entry MUST be configured as an environment variable on the resource represented by application
.
A Service Binding resource MUST define a .status.conditions
which is an array of Condition
objects. A Condition
object MUST define type
, status
, and lastTransitionTime
entries. At least one condition containing a type
of Ready
MUST be defined. The status
of the Ready
condition MUST have a value of True
, False
, or Unknown
. The lastTransitionTime
MUST contain the last time that the condition transitioned from one status to another. A Service Binding resource MAY define reason
and message
entries to describe the last status
transition. As label selectors are inherently queries that return zero-to-many resources, it is RECOMMENDED that ServiceBinding
authors use a combination of labels that yield a single resource, but implementors MUST handle each matching resource as if it was specified by name in a distinct ServiceBinding
resource. Partial failures MUST be aggregated and reported on the binding status's Ready
condition. A Service Binding resource SHOULD reflect the secret projected into the application as .status.binding.name
.
apiVersion: service.binding/v1alpha2
kind: ServiceBinding
metadata:
name: # string
generation: # int64, defined by the Kubernetes control plane
...
spec:
name: # string, optional, default: .metadata.name
type: # string, optional
provider: # string, optional
application: # PodSpec-able resource ObjectReference-like
apiVersion: # string
kind: # string
name: # string, mutually exclusive with selector
selector: # metav1.LabelSelector, mutually exclusive with name
containers: # []intstr.IntOrString, optional
service: # Provisioned Service-able resource ObjectReference-like
apiVersion: # string
kind: # string
name: # string
mappings: # []Mapping, optional
- name: # string
value: # string
env: # []EnvVar, optional
- name: # string
key: # string
status:
binding: # LocalObjectReference, optional
name: # string
conditions: # []Condition containing at least one entry for `Ready`
- type: # string
status: # string
lastTransitionTime: # Time
reason: # string
message: # string
observedGeneration: # int64
apiVersion: service.binding/v1alpha2
kind: ServiceBinding
metadata:
name: account-service
spec:
application:
apiVersion: apps/v1
kind: Deployment
name: online-banking
service:
apiVersion: com.example/v1alpha1
kind: AccountService
name: prod-account-service
status:
conditions:
- type: Ready
status: 'True'
apiVersion: service.binding/v1alpha2
kind: ServiceBinding
metadata:
name: online-banking-frontend-to-account-service
spec:
name: account-service
application:
apiVersion: apps/v1
kind: Deployment
selector:
matchLabels:
app.kubernetes.io/part-of: online-banking
app.kubernetes.io/component: frontend
service:
apiVersion: com.example/v1alpha1
kind: AccountService
name: prod-account-service
status:
conditions:
- type: Ready
status: 'True'
apiVersion: service.binding/v1alpha2
kind: ServiceBinding
metadata:
name: account-service
spec:
application:
apiVersion: apps/v1
kind: Deployment
name: online-banking
service:
apiVersion: com.example/v1alpha1
kind: AccountService
name: prod-account-service
mappings:
- name: accountServiceUri
value: https://{{ urlquery .username }}:{{ urlquery .password }}@{{ .host }}:{{ .port }}/{{ .path }}
status:
binding:
name: prod-account-service-projection
conditions:
- type: Ready
status: 'True'
apiVersion: service.binding/v1alpha2
kind: ServiceBinding
metadata:
name: account-service
spec:
application:
apiVersion: apps/v1
kind: Deployment
name: online-banking
service:
apiVersion: com.example/v1alpha1
kind: AccountService
name: prod-account-service
mappings:
- name: accountServiceUri
value: https://{{ urlquery .username }}:{{ urlquery .password }}@{{ .host }}:{{ .port }}/{{ .path }}
env:
- name: ACCOUNT_SERVICE_HOST
key: host
- name: ACCOUNT_SERVICE_USERNAME
key: username
- name: ACCOUNT_SERVICE_PASSWORD
key: password
- name: ACCOUNT_SERVICE_URI
key: accountServiceUri
status:
binding:
name: prod-account-service-projection
conditions:
- type: Ready
status: 'True'
A Reconciler implementation for the ServiceBinding
type is responsible for binding the Provisioned Service binding Secret
into an Application. The Secret
referred to by .status.binding.name
on the resource represented by service
MUST be mounted as a volume on the resource represented by application
.
If a .spec.name
is set, the directory name of the volume mount MUST be its value. If a .spec.name
is not set, the directory name of the volume mount SHOULD be the value of .metadata.name
.
If the $SERVICE_BINDING_ROOT
environment variable has already been configured on the resource represented by application
, the Provisioned Service binding Secret
MUST be mounted relative to that location. If the $SERVICE_BINDING_ROOT
environment variable has not been configured on the resource represented by application
, the $SERVICE_BINDING_ROOT
environment variable MUST be set and the Provisioned Service binding Secret
MUST be mounted relative to that location. A RECOMMENDED value to use is /bindings
.
The $SERVICE_BINDING_ROOT
environment variable MUST NOT be reset if it is already configured on the resource represented by application
.
If a .spec.type
is set, the type
entry in the binding Secret
MUST be set to its value overriding any existing value. If a .spec.provider
is set, the provider
entry in the binding Secret
MUST be set to its value overriding any existing value.
If the modification of the Application resource is completed successfully, the Ready
condition status MUST be set to True
. If the modification of the Application resource is not completed successfully the Ready
condition status MUST NOT be set to True
.
Extensions are optional additions to the core specification as defined above. Implementation and support of these specifications are not required in order for a platform to be considered compliant. However, if the features addressed by these specifications are supported a platform MUST be in compliance with the specification that governs that feature.
There are scenarios where the Reconciler that processes a ServiceBinding
(hereinafter referred to as "Reconciler A") is different than the Reconciler that will project the binding into the Application (hereinafter referred to as "Reconciler B"). To transfer the projection responsibility from Reconciler A to Reconciler B the ServiceBinding
author MUST set the projection.service.binding/type
annotation to Custom
. An exemplar CRD can be found here.
Reconciler A reacts to this annotation by creating a ServiceBindingProjection
which includes the necessary information for Reconciler B. Reconciler B takes responsibility for the ServiceBindingProjection
resource to perform the projection. Reconciler A is responsible for updating the ServiceBindingProjection
's .spec
upon corresponding changes to ServiceBinding
.
Reconciler B MUST set the ServiceBindingProjection
's Ready
condition according to the rules set in Ready Condition Status. Reconciler A MUST reflect ServiceBindingProjection
's Ready
condition with an additional ProjectionReady
condition on ServiceBinding
. The Ready
condition of ServiceBinding
MUST NOT be True
unless ProjectionReady
is True
.
apiVersion: service.binding/v1alpha2
kind: ServiceBinding
metadata:
name: account-service
annotations:
projection.service.binding/type: "Custom"
spec:
application:
apiVersion: apps/v1
kind: Deployment
name: online-banking
service:
apiVersion: com.example/v1alpha1
kind: AccountService
name: prod-account-service
status:
binding:
name: prod-account-service-projection
conditions:
- type: Ready
status: 'True'
- type: ProjectionReady
status: 'True'
apiVersion: internal.service.binding/v1alpha2
kind: ServiceBindingProjection
metadata:
name: # string
generation: # int64, defined by the Kubernetes control plane
...
spec:
name: # string
binding: # LocalObjectReference
application: # ObjectReference-like
apiVersion: # string
kind: # string
name: # string, mutually exclusive with selector
selector: # metav1.LabelSelector, mutually exclusive with name
containers: # []intstr.IntOrString, optional
env: # []EnvVar, optional
- name: # string
key: # string
status:
conditions: # []Condition containing at least one entry for `Ready`
- type: # string
status: # string
lastTransitionTime: # Time
reason: # string
message: # string
observedGeneration: # int64
apiVersion: internal.service.binding/v1alpha2
kind: ServiceBindingProjection
metadata:
name: account-service
spec:
binding: prod-account-service-projection
application:
apiVersion: apps/v1
kind: Deployment
name: online-banking
status:
conditions:
- type: Ready
status: 'True'
Many services, especially initially, will not be Provisioned Service-compliant. These services will expose the appropriate binding Secret
information, but not in the way that the specification or applications expect. Users should have a way of describing a mapping from existing data associated with arbitrary resources and CRDs to a representation of a binding Secret
.
To handle the majority of existing resources and CRDs, Secret
generation needs to support the following behaviors:
- Extract a string from a resource
- Extract an entire
ConfigMap
/Secret
refrenced from a resource - Extract a specific entry in a
ConfigMap
/Secret
referenced from a resource - Extract entries from a collection of objects, mapping keys and values from entries in a
ConfigMap
/Secret
referenced from a resource - Map each value to a specific key
While the syntax of the generation strategies are specific to the system they are annotating, they are based on a common data model.
Model | Description |
---|---|
path |
A template represention of the path to an element in a Kubernetes resource. The value of path is specified as JSONPath. Required. |
objectType |
Specifies the type of the object selected by the path . One of ConfigMap , Secret , or string (default). |
elementType |
Specifies the type of object in an array selected by the path . One of sliceOfMaps , sliceOfStrings , string (default). |
sourceKey |
Specifies a particular key to select if a ConfigMap or Secret is selected by the path . Specifies a value to use for the key for an entry in a binding Secret when elementType is sliceOfMaps . |
sourceValue |
Specifies a particular value to use for the value for an entry in a binding Secret when elementType is sliceOfMaps |
OLM Operators are configured by setting the specDescriptor
and statusDescriptor
entries in the ClusterServiceVersion with mapping descriptors.
The following examples refer to this resource definition.
apiVersion: apps.kube.io/v1beta1
kind: Database
metadata:
name: my-cluster
spec:
...
status:
bootstrap:
- type: plain
url: myhost2.example.com
name: hostGroup1
- type: tls
url: myhost1.example.com:9092,myhost2.example.com:9092
name: hostGroup2
data:
dbConfiguration: database-config # ConfigMap
dbCredentials: database-cred-Secret # Secret
url: db.stage.ibm.com
-
Mount an entire
Secret
as the bindingSecret
- path: data.dbCredentials x-descriptors: - urn:alm:descriptor:io.kubernetes:Secret - service.binding
-
Mount an entire
ConfigMap
as the bindingSecret
- path: data.dbConfiguration x-descriptors: - urn:alm:descriptor:io.kubernetes:ConfigMap - service.binding
-
Mount an entry from a
ConfigMap
into the bindingSecret
- path: data.dbConfiguration x-descriptors: - urn:alm:descriptor:io.kubernetes:ConfigMap - service.binding:certificate:sourceKey=certificate
-
Mount an entry from a
ConfigMap
into the bindingSecret
with a different key- path: data.dbConfiguration x-descriptors: - urn:alm:descriptor:io.kubernetes:ConfigMap - servicebinding:timeout:sourceKey=db_timeout
-
Mount a resource definition value into the binding
Secret
- path: data.uri x-descriptors: - service.binding:uri
-
Mount a resource definition value into the binding
Secret
with a different key- path: data.connectionURL x-descriptors: - service.binding:uri
-
Mount the entries of a collection into the binding
Secret
selecting the key and value from each entry- path: bootstrap x-descriptors: - service.binding:endpoints:elementType=sliceOfMaps:sourceKey=type:sourceValue=url
Non-OLM Operators are configured by adding annotations to the Operator's CRD with mapping configuration. All Kubernetes resources are configured by adding annotations to the resource.
The following examples refer to this resource definition.
apiVersion: apps.kube.io/v1beta1
kind: Database
metadata:
name: my-cluster
spec:
...
status:
bootstrap:
- type: plain
url: myhost2.example.com
name: hostGroup1
- type: tls
url: myhost1.example.com:9092,myhost2.example.com:9092
name: hostGroup2
data:
dbConfiguration: database-config # ConfigMap
dbCredentials: database-cred-Secret # Secret
url: db.stage.ibm.com
- Mount an entire
Secret
as the bindingSecret
“service.binding": ”path={.status.data.dbCredentials},objectType=Secret”
- Mount an entire
ConfigMap
as the bindingSecret
service.binding”: "path={.status.data.dbConfiguration},objectType=ConfigMap”
- Mount an entry from a
ConfigMap
into the bindingSecret
“service.binding/certificate”: "path={.status.data.dbConfiguration},objectType=ConfigMap,sourceKey=certificate"
- Mount an entry from a
ConfigMap
into the bindingSecret
with a different key“service.binding/timeout”: “path={.status.data.dbConfiguration},objectType=ConfigMap,sourceKey=db_timeout”
- Mount a resource definition value into the binding
Secret
“service.binding/uri”: "path={.status.data.url}"
- Mount a resource definition value into the binding
Secret
with a different key“service.binding/uri": "path={.status.data.connectionURL}”
- Mount the entries of a collection into the binding
Secret
selecting the key and value from each entry“service.binding/endpoints”: "path={.status.bootstrap},elementType=sliceOfMaps,sourceKey=type,sourceValue=url"
Kubernetes clusters often utilize Role-based access control (RBAC) to authorize subjects to perform specific actions on resources. When operating in a cluster with RBAC enabled, the service binding reconciler needs permission to read resources that provisioned a service and write resources that services are projected into. This extension defines a means for third-party CRD authors and cluster operators to expose resources to the service binding reconciler. Cluster operators MAY impose additional access controls beyond RBAC.
Cluster operators and CRD authors MAY opt-in resources to service binding by defining a ClusterRole
with a label matching service.binding/controller=true
. For Provisioned Service-able resources the get
, list
, and watch
verbs MUST be granted. For PodSpec-able resources the get
, list
, watch
, update
, and patch
verbs MUST be granted.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: awesome-service-bindings
labels:
service.binding/controller: "true" # matches the aggregation rule selector
rules:
# for Provisioned Service-able resources only
- apiGroups:
- awesome.example.com
resources:
- awesomeservices
verbs:
- get
- list
- watch
# for PodSpec-able resources (also compatible with Provisioned Service-able resources)
- apiGroups:
- awesome.example.com
resources:
- awesomeapplications
verbs:
- get
- list
- watch
- update
- patch
Service binding reconciler implementations MUST define an aggregated ClusterRole
with a label selector matching the label service.binding/controller=true
. This ClusterRole
MUST be bound (RoleBinding
for a single namespace or ClusterRoleBinding
if cluster-wide) to the subject the service binding reconciler runs as, typically a ServiceAccount
.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ...
aggregationRule:
clusterRoleSelectors:
- matchLabels:
service.binding/controller: "true"
rules: [] # The control plane automatically fills in the rules