Introduce AuthorizationPolicy CRDs (#8007)

Issue #7709 proposes new Custom Resource types to support generalized
authorization policies:

- `AuthorizationPolicy`
- `MeshTLSAuthentication`
- `NetworkAuthentication`

This change introduces these CRDs to the default linkerd installation
(via the `linkerd-crds` chart) and updates the policy controller's
to handle these resource types. The policy admission controller
validates that these resource reference only suppported types.

This new functionality is tested at multiple levels:

* `linkerd-policy-controller-k8s-index` includes unit tests for the
  indexer to test how events update the index;
* `linkerd-policy-test` includes integration tests that run in-cluster
  to validate that the gRPC API updates as resources are manipulated;
* `linkerd-policy-test` includes integration tests that exercise the
  admission controller's resource validation; and
* `linkerd-policy-test` includes integration tests that ensure that
  proxies honor authorization resources.

This change does NOT update Linkerd's control plane and extensions to
use these new authorization primitives. Furthermore, the `linkerd` CLI
does not yet support inspecting these new resource types. These
enhancements will be made in followup changes.

Signed-off-by: Oliver Gould <ver@buoyant.io>
This commit is contained in:
Oliver Gould 2022-03-30 12:26:45 -07:00 committed by GitHub
parent 24079ab076
commit c1a1430d1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 6833 additions and 510 deletions

View File

@ -958,6 +958,7 @@ dependencies = [
"futures",
"linkerd-policy-controller-core",
"linkerd2-proxy-api",
"maplit",
"tokio",
"tonic",
"tracing",
@ -989,6 +990,7 @@ dependencies = [
"kubert",
"linkerd-policy-controller-core",
"linkerd-policy-controller-k8s-api",
"maplit",
"parking_lot",
"tokio",
"tokio-stream",

View File

@ -166,6 +166,9 @@ webhooks:
apiGroups: ["policy.linkerd.io"]
apiVersions: ["v1alpha1", "v1beta1"]
resources:
- authorizationpolicies
- networkauthentications
- meshtlsauthentications
- serverauthorizations
- servers
sideEffects: None
@ -190,6 +193,9 @@ rules:
- apiGroups:
- policy.linkerd.io
resources:
- authorizationpolicies
- meshtlsauthentications
- networkauthentications
- servers
- serverauthorizations
verbs:

View File

@ -0,0 +1,99 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: authorizationpolicies.policy.linkerd.io
annotations:
{{ include "partials.annotations.created-by" . }}
labels:
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
linkerd.io/control-plane-ns: {{.Release.Namespace}}
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: AuthorizationPolicy
plural: authorizationpolicies
singular: authorizationpolicy
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
Authorizes clients to communicate with Linkerd-proxied server
resources.
type: object
required: [targetRef, requiredAuthenticationRefs]
properties:
targetRef:
description: >-
TargetRef references a resource to which the authorization
policy applies.
type: object
required: [kind, name]
# Modified from the gateway API.
# Copyright 2020 The Kubernetes Authors
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred.
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
requiredAuthenticationRefs:
description: >-
RequiredAuthenticationRefs enumerates a set of required
authentications. ALL authentications must be satisfied for
the authorization to apply. If any of the referred objects
cannot be found, the authorization will be ignored.
type: array
items:
type: object
required: [kind, name]
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string

View File

@ -0,0 +1,86 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: meshtlsauthentications.policy.linkerd.io
annotations:
{{ include "partials.annotations.created-by" . }}
labels:
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
linkerd.io/control-plane-ns: {{.Release.Namespace}}
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: MeshTLSAuthentication
plural: meshtlsauthentications
singular: meshtlsauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
MeshTLSAuthentication defines a list of authenticated client IDs
to be referenced by an `AuthorizationPolicy`. If a client
connection has the mutually-authenticated identity that matches
ANY of the of the provided identities, the connection is
considered authenticated.
type: object
oneOf:
- required: [identities]
- required: [identityRefs]
properties:
identities:
description: >-
Authorizes clients with the provided proxy identity strings
(as provided via MTLS)
The `*` prefix can be used to match all identities in
a domain. An identity string of `*` indicates that
all authentication clients are authorized.
type: array
items:
type: string
pattern: '^(\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
identityRefs:
type: array
items:
type: object
required:
- kind
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent. When unspecified,
this refers to all resources of the specified Group
and Kind in the specified namespace.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string

View File

@ -0,0 +1,53 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: networkauthentications.policy.linkerd.io
annotations:
{{ include "partials.annotations.created-by" . }}
labels:
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
linkerd.io/control-plane-ns: {{.Release.Namespace}}
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: NetworkAuthentication
plural: networkauthentications
singular: networkauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
NetworkAuthentication defines a list of authenticated client
networks to be referenced by an `AuthorizationPolicy`. If a
client connection originates from ANY of the of the provided
networks, the connection is considered authenticated.
type: object
required: [networks]
properties:
networks:
type: array
items:
type: object
required: [cidr]
properties:
cidr:
description: >-
The CIDR of the network to be authorized.
type: string
except:
description: >-
A list of IP networks/addresses not to be included in
the above `cidr`.
type: array
items:
type: string

View File

@ -61,6 +61,9 @@ Otherwise, you can use the --ignore-cluster flag to overwrite the existing globa
var (
templatesCrdFiles = []string{
"templates/policy/authorization-policy.yaml",
"templates/policy/meshtls-authentication.yaml",
"templates/policy/network-authentication.yaml",
"templates/policy/server.yaml",
"templates/policy/server-authorization.yaml",
"templates/serviceprofile.yaml",

View File

@ -1,6 +1,244 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: authorizationpolicies.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: AuthorizationPolicy
plural: authorizationpolicies
singular: authorizationpolicy
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
Authorizes clients to communicate with Linkerd-proxied server
resources.
type: object
required: [targetRef, requiredAuthenticationRefs]
properties:
targetRef:
description: >-
TargetRef references a resource to which the authorization
policy applies.
type: object
required: [kind, name]
# Modified from the gateway API.
# Copyright 2020 The Kubernetes Authors
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred.
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
requiredAuthenticationRefs:
description: >-
RequiredAuthenticationRefs enumerates a set of required
authentications. ALL authentications must be satisfied for
the authorization to apply. If any of the referred objects
cannot be found, the authorization will be ignored.
type: array
items:
type: object
required: [kind, name]
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: meshtlsauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: MeshTLSAuthentication
plural: meshtlsauthentications
singular: meshtlsauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
MeshTLSAuthentication defines a list of authenticated client IDs
to be referenced by an `AuthorizationPolicy`. If a client
connection has the mutually-authenticated identity that matches
ANY of the of the provided identities, the connection is
considered authenticated.
type: object
oneOf:
- required: [identities]
- required: [identityRefs]
properties:
identities:
description: >-
Authorizes clients with the provided proxy identity strings
(as provided via MTLS)
The `*` prefix can be used to match all identities in
a domain. An identity string of `*` indicates that
all authentication clients are authorized.
type: array
items:
type: string
pattern: '^(\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
identityRefs:
type: array
items:
type: object
required:
- kind
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent. When unspecified,
this refers to all resources of the specified Group
and Kind in the specified namespace.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: networkauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: NetworkAuthentication
plural: networkauthentications
singular: networkauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
NetworkAuthentication defines a list of authenticated client
networks to be referenced by an `AuthorizationPolicy`. If a
client connection originates from ANY of the of the provided
networks, the connection is considered authenticated.
type: object
required: [networks]
properties:
networks:
type: array
items:
type: object
required: [cidr]
properties:
cidr:
description: >-
The CIDR of the network to be authorized.
type: string
except:
description: >-
A list of IP networks/addresses not to be included in
the above `cidr`.
type: array
items:
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: servers.policy.linkerd.io
annotations:
@ -859,6 +1097,9 @@ webhooks:
apiGroups: ["policy.linkerd.io"]
apiVersions: ["v1alpha1", "v1beta1"]
resources:
- authorizationpolicies
- networkauthentications
- meshtlsauthentications
- serverauthorizations
- servers
sideEffects: None
@ -883,6 +1124,9 @@ rules:
- apiGroups:
- policy.linkerd.io
resources:
- authorizationpolicies
- meshtlsauthentications
- networkauthentications
- servers
- serverauthorizations
verbs:

View File

@ -1,6 +1,244 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: authorizationpolicies.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: AuthorizationPolicy
plural: authorizationpolicies
singular: authorizationpolicy
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
Authorizes clients to communicate with Linkerd-proxied server
resources.
type: object
required: [targetRef, requiredAuthenticationRefs]
properties:
targetRef:
description: >-
TargetRef references a resource to which the authorization
policy applies.
type: object
required: [kind, name]
# Modified from the gateway API.
# Copyright 2020 The Kubernetes Authors
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred.
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
requiredAuthenticationRefs:
description: >-
RequiredAuthenticationRefs enumerates a set of required
authentications. ALL authentications must be satisfied for
the authorization to apply. If any of the referred objects
cannot be found, the authorization will be ignored.
type: array
items:
type: object
required: [kind, name]
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: meshtlsauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: MeshTLSAuthentication
plural: meshtlsauthentications
singular: meshtlsauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
MeshTLSAuthentication defines a list of authenticated client IDs
to be referenced by an `AuthorizationPolicy`. If a client
connection has the mutually-authenticated identity that matches
ANY of the of the provided identities, the connection is
considered authenticated.
type: object
oneOf:
- required: [identities]
- required: [identityRefs]
properties:
identities:
description: >-
Authorizes clients with the provided proxy identity strings
(as provided via MTLS)
The `*` prefix can be used to match all identities in
a domain. An identity string of `*` indicates that
all authentication clients are authorized.
type: array
items:
type: string
pattern: '^(\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
identityRefs:
type: array
items:
type: object
required:
- kind
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent. When unspecified,
this refers to all resources of the specified Group
and Kind in the specified namespace.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: networkauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: NetworkAuthentication
plural: networkauthentications
singular: networkauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
NetworkAuthentication defines a list of authenticated client
networks to be referenced by an `AuthorizationPolicy`. If a
client connection originates from ANY of the of the provided
networks, the connection is considered authenticated.
type: object
required: [networks]
properties:
networks:
type: array
items:
type: object
required: [cidr]
properties:
cidr:
description: >-
The CIDR of the network to be authorized.
type: string
except:
description: >-
A list of IP networks/addresses not to be included in
the above `cidr`.
type: array
items:
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: servers.policy.linkerd.io
annotations:
@ -859,6 +1097,9 @@ webhooks:
apiGroups: ["policy.linkerd.io"]
apiVersions: ["v1alpha1", "v1beta1"]
resources:
- authorizationpolicies
- networkauthentications
- meshtlsauthentications
- serverauthorizations
- servers
sideEffects: None
@ -883,6 +1124,9 @@ rules:
- apiGroups:
- policy.linkerd.io
resources:
- authorizationpolicies
- meshtlsauthentications
- networkauthentications
- servers
- serverauthorizations
verbs:

View File

@ -1,6 +1,244 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: authorizationpolicies.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: AuthorizationPolicy
plural: authorizationpolicies
singular: authorizationpolicy
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
Authorizes clients to communicate with Linkerd-proxied server
resources.
type: object
required: [targetRef, requiredAuthenticationRefs]
properties:
targetRef:
description: >-
TargetRef references a resource to which the authorization
policy applies.
type: object
required: [kind, name]
# Modified from the gateway API.
# Copyright 2020 The Kubernetes Authors
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred.
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
requiredAuthenticationRefs:
description: >-
RequiredAuthenticationRefs enumerates a set of required
authentications. ALL authentications must be satisfied for
the authorization to apply. If any of the referred objects
cannot be found, the authorization will be ignored.
type: array
items:
type: object
required: [kind, name]
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: meshtlsauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: MeshTLSAuthentication
plural: meshtlsauthentications
singular: meshtlsauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
MeshTLSAuthentication defines a list of authenticated client IDs
to be referenced by an `AuthorizationPolicy`. If a client
connection has the mutually-authenticated identity that matches
ANY of the of the provided identities, the connection is
considered authenticated.
type: object
oneOf:
- required: [identities]
- required: [identityRefs]
properties:
identities:
description: >-
Authorizes clients with the provided proxy identity strings
(as provided via MTLS)
The `*` prefix can be used to match all identities in
a domain. An identity string of `*` indicates that
all authentication clients are authorized.
type: array
items:
type: string
pattern: '^(\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
identityRefs:
type: array
items:
type: object
required:
- kind
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent. When unspecified,
this refers to all resources of the specified Group
and Kind in the specified namespace.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: networkauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: NetworkAuthentication
plural: networkauthentications
singular: networkauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
NetworkAuthentication defines a list of authenticated client
networks to be referenced by an `AuthorizationPolicy`. If a
client connection originates from ANY of the of the provided
networks, the connection is considered authenticated.
type: object
required: [networks]
properties:
networks:
type: array
items:
type: object
required: [cidr]
properties:
cidr:
description: >-
The CIDR of the network to be authorized.
type: string
except:
description: >-
A list of IP networks/addresses not to be included in
the above `cidr`.
type: array
items:
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: servers.policy.linkerd.io
annotations:
@ -859,6 +1097,9 @@ webhooks:
apiGroups: ["policy.linkerd.io"]
apiVersions: ["v1alpha1", "v1beta1"]
resources:
- authorizationpolicies
- networkauthentications
- meshtlsauthentications
- serverauthorizations
- servers
sideEffects: None
@ -883,6 +1124,9 @@ rules:
- apiGroups:
- policy.linkerd.io
resources:
- authorizationpolicies
- meshtlsauthentications
- networkauthentications
- servers
- serverauthorizations
verbs:

View File

@ -1,6 +1,244 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: authorizationpolicies.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: AuthorizationPolicy
plural: authorizationpolicies
singular: authorizationpolicy
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
Authorizes clients to communicate with Linkerd-proxied server
resources.
type: object
required: [targetRef, requiredAuthenticationRefs]
properties:
targetRef:
description: >-
TargetRef references a resource to which the authorization
policy applies.
type: object
required: [kind, name]
# Modified from the gateway API.
# Copyright 2020 The Kubernetes Authors
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred.
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
requiredAuthenticationRefs:
description: >-
RequiredAuthenticationRefs enumerates a set of required
authentications. ALL authentications must be satisfied for
the authorization to apply. If any of the referred objects
cannot be found, the authorization will be ignored.
type: array
items:
type: object
required: [kind, name]
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: meshtlsauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: MeshTLSAuthentication
plural: meshtlsauthentications
singular: meshtlsauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
MeshTLSAuthentication defines a list of authenticated client IDs
to be referenced by an `AuthorizationPolicy`. If a client
connection has the mutually-authenticated identity that matches
ANY of the of the provided identities, the connection is
considered authenticated.
type: object
oneOf:
- required: [identities]
- required: [identityRefs]
properties:
identities:
description: >-
Authorizes clients with the provided proxy identity strings
(as provided via MTLS)
The `*` prefix can be used to match all identities in
a domain. An identity string of `*` indicates that
all authentication clients are authorized.
type: array
items:
type: string
pattern: '^(\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
identityRefs:
type: array
items:
type: object
required:
- kind
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent. When unspecified,
this refers to all resources of the specified Group
and Kind in the specified namespace.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: networkauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: NetworkAuthentication
plural: networkauthentications
singular: networkauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
NetworkAuthentication defines a list of authenticated client
networks to be referenced by an `AuthorizationPolicy`. If a
client connection originates from ANY of the of the provided
networks, the connection is considered authenticated.
type: object
required: [networks]
properties:
networks:
type: array
items:
type: object
required: [cidr]
properties:
cidr:
description: >-
The CIDR of the network to be authorized.
type: string
except:
description: >-
A list of IP networks/addresses not to be included in
the above `cidr`.
type: array
items:
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: servers.policy.linkerd.io
annotations:
@ -859,6 +1097,9 @@ webhooks:
apiGroups: ["policy.linkerd.io"]
apiVersions: ["v1alpha1", "v1beta1"]
resources:
- authorizationpolicies
- networkauthentications
- meshtlsauthentications
- serverauthorizations
- servers
sideEffects: None
@ -883,6 +1124,9 @@ rules:
- apiGroups:
- policy.linkerd.io
resources:
- authorizationpolicies
- meshtlsauthentications
- networkauthentications
- servers
- serverauthorizations
verbs:

View File

@ -1,6 +1,244 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: authorizationpolicies.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: AuthorizationPolicy
plural: authorizationpolicies
singular: authorizationpolicy
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
Authorizes clients to communicate with Linkerd-proxied server
resources.
type: object
required: [targetRef, requiredAuthenticationRefs]
properties:
targetRef:
description: >-
TargetRef references a resource to which the authorization
policy applies.
type: object
required: [kind, name]
# Modified from the gateway API.
# Copyright 2020 The Kubernetes Authors
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred.
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
requiredAuthenticationRefs:
description: >-
RequiredAuthenticationRefs enumerates a set of required
authentications. ALL authentications must be satisfied for
the authorization to apply. If any of the referred objects
cannot be found, the authorization will be ignored.
type: array
items:
type: object
required: [kind, name]
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: meshtlsauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: MeshTLSAuthentication
plural: meshtlsauthentications
singular: meshtlsauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
MeshTLSAuthentication defines a list of authenticated client IDs
to be referenced by an `AuthorizationPolicy`. If a client
connection has the mutually-authenticated identity that matches
ANY of the of the provided identities, the connection is
considered authenticated.
type: object
oneOf:
- required: [identities]
- required: [identityRefs]
properties:
identities:
description: >-
Authorizes clients with the provided proxy identity strings
(as provided via MTLS)
The `*` prefix can be used to match all identities in
a domain. An identity string of `*` indicates that
all authentication clients are authorized.
type: array
items:
type: string
pattern: '^(\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
identityRefs:
type: array
items:
type: object
required:
- kind
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent. When unspecified,
this refers to all resources of the specified Group
and Kind in the specified namespace.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: networkauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: NetworkAuthentication
plural: networkauthentications
singular: networkauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
NetworkAuthentication defines a list of authenticated client
networks to be referenced by an `AuthorizationPolicy`. If a
client connection originates from ANY of the of the provided
networks, the connection is considered authenticated.
type: object
required: [networks]
properties:
networks:
type: array
items:
type: object
required: [cidr]
properties:
cidr:
description: >-
The CIDR of the network to be authorized.
type: string
except:
description: >-
A list of IP networks/addresses not to be included in
the above `cidr`.
type: array
items:
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: servers.policy.linkerd.io
annotations:
@ -859,6 +1097,9 @@ webhooks:
apiGroups: ["policy.linkerd.io"]
apiVersions: ["v1alpha1", "v1beta1"]
resources:
- authorizationpolicies
- networkauthentications
- meshtlsauthentications
- serverauthorizations
- servers
sideEffects: None
@ -883,6 +1124,9 @@ rules:
- apiGroups:
- policy.linkerd.io
resources:
- authorizationpolicies
- meshtlsauthentications
- networkauthentications
- servers
- serverauthorizations
verbs:

View File

@ -1,6 +1,244 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: authorizationpolicies.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: AuthorizationPolicy
plural: authorizationpolicies
singular: authorizationpolicy
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
Authorizes clients to communicate with Linkerd-proxied server
resources.
type: object
required: [targetRef, requiredAuthenticationRefs]
properties:
targetRef:
description: >-
TargetRef references a resource to which the authorization
policy applies.
type: object
required: [kind, name]
# Modified from the gateway API.
# Copyright 2020 The Kubernetes Authors
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred.
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
requiredAuthenticationRefs:
description: >-
RequiredAuthenticationRefs enumerates a set of required
authentications. ALL authentications must be satisfied for
the authorization to apply. If any of the referred objects
cannot be found, the authorization will be ignored.
type: array
items:
type: object
required: [kind, name]
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: meshtlsauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: MeshTLSAuthentication
plural: meshtlsauthentications
singular: meshtlsauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
MeshTLSAuthentication defines a list of authenticated client IDs
to be referenced by an `AuthorizationPolicy`. If a client
connection has the mutually-authenticated identity that matches
ANY of the of the provided identities, the connection is
considered authenticated.
type: object
oneOf:
- required: [identities]
- required: [identityRefs]
properties:
identities:
description: >-
Authorizes clients with the provided proxy identity strings
(as provided via MTLS)
The `*` prefix can be used to match all identities in
a domain. An identity string of `*` indicates that
all authentication clients are authorized.
type: array
items:
type: string
pattern: '^(\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
identityRefs:
type: array
items:
type: object
required:
- kind
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent. When unspecified,
this refers to all resources of the specified Group
and Kind in the specified namespace.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: networkauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: NetworkAuthentication
plural: networkauthentications
singular: networkauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
NetworkAuthentication defines a list of authenticated client
networks to be referenced by an `AuthorizationPolicy`. If a
client connection originates from ANY of the of the provided
networks, the connection is considered authenticated.
type: object
required: [networks]
properties:
networks:
type: array
items:
type: object
required: [cidr]
properties:
cidr:
description: >-
The CIDR of the network to be authorized.
type: string
except:
description: >-
A list of IP networks/addresses not to be included in
the above `cidr`.
type: array
items:
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: servers.policy.linkerd.io
annotations:
@ -859,6 +1097,9 @@ webhooks:
apiGroups: ["policy.linkerd.io"]
apiVersions: ["v1alpha1", "v1beta1"]
resources:
- authorizationpolicies
- networkauthentications
- meshtlsauthentications
- serverauthorizations
- servers
sideEffects: None
@ -883,6 +1124,9 @@ rules:
- apiGroups:
- policy.linkerd.io
resources:
- authorizationpolicies
- meshtlsauthentications
- networkauthentications
- servers
- serverauthorizations
verbs:

View File

@ -1,6 +1,244 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: authorizationpolicies.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: AuthorizationPolicy
plural: authorizationpolicies
singular: authorizationpolicy
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
Authorizes clients to communicate with Linkerd-proxied server
resources.
type: object
required: [targetRef, requiredAuthenticationRefs]
properties:
targetRef:
description: >-
TargetRef references a resource to which the authorization
policy applies.
type: object
required: [kind, name]
# Modified from the gateway API.
# Copyright 2020 The Kubernetes Authors
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred.
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
requiredAuthenticationRefs:
description: >-
RequiredAuthenticationRefs enumerates a set of required
authentications. ALL authentications must be satisfied for
the authorization to apply. If any of the referred objects
cannot be found, the authorization will be ignored.
type: array
items:
type: object
required: [kind, name]
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: meshtlsauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: MeshTLSAuthentication
plural: meshtlsauthentications
singular: meshtlsauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
MeshTLSAuthentication defines a list of authenticated client IDs
to be referenced by an `AuthorizationPolicy`. If a client
connection has the mutually-authenticated identity that matches
ANY of the of the provided identities, the connection is
considered authenticated.
type: object
oneOf:
- required: [identities]
- required: [identityRefs]
properties:
identities:
description: >-
Authorizes clients with the provided proxy identity strings
(as provided via MTLS)
The `*` prefix can be used to match all identities in
a domain. An identity string of `*` indicates that
all authentication clients are authorized.
type: array
items:
type: string
pattern: '^(\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
identityRefs:
type: array
items:
type: object
required:
- kind
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent. When unspecified,
this refers to all resources of the specified Group
and Kind in the specified namespace.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: networkauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: NetworkAuthentication
plural: networkauthentications
singular: networkauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
NetworkAuthentication defines a list of authenticated client
networks to be referenced by an `AuthorizationPolicy`. If a
client connection originates from ANY of the of the provided
networks, the connection is considered authenticated.
type: object
required: [networks]
properties:
networks:
type: array
items:
type: object
required: [cidr]
properties:
cidr:
description: >-
The CIDR of the network to be authorized.
type: string
except:
description: >-
A list of IP networks/addresses not to be included in
the above `cidr`.
type: array
items:
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: servers.policy.linkerd.io
annotations:
@ -859,6 +1097,9 @@ webhooks:
apiGroups: ["policy.linkerd.io"]
apiVersions: ["v1alpha1", "v1beta1"]
resources:
- authorizationpolicies
- networkauthentications
- meshtlsauthentications
- serverauthorizations
- servers
sideEffects: None
@ -883,6 +1124,9 @@ rules:
- apiGroups:
- policy.linkerd.io
resources:
- authorizationpolicies
- meshtlsauthentications
- networkauthentications
- servers
- serverauthorizations
verbs:

View File

@ -1,6 +1,244 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: authorizationpolicies.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: AuthorizationPolicy
plural: authorizationpolicies
singular: authorizationpolicy
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
Authorizes clients to communicate with Linkerd-proxied server
resources.
type: object
required: [targetRef, requiredAuthenticationRefs]
properties:
targetRef:
description: >-
TargetRef references a resource to which the authorization
policy applies.
type: object
required: [kind, name]
# Modified from the gateway API.
# Copyright 2020 The Kubernetes Authors
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred.
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
requiredAuthenticationRefs:
description: >-
RequiredAuthenticationRefs enumerates a set of required
authentications. ALL authentications must be satisfied for
the authorization to apply. If any of the referred objects
cannot be found, the authorization will be ignored.
type: array
items:
type: object
required: [kind, name]
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: meshtlsauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: MeshTLSAuthentication
plural: meshtlsauthentications
singular: meshtlsauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
MeshTLSAuthentication defines a list of authenticated client IDs
to be referenced by an `AuthorizationPolicy`. If a client
connection has the mutually-authenticated identity that matches
ANY of the of the provided identities, the connection is
considered authenticated.
type: object
oneOf:
- required: [identities]
- required: [identityRefs]
properties:
identities:
description: >-
Authorizes clients with the provided proxy identity strings
(as provided via MTLS)
The `*` prefix can be used to match all identities in
a domain. An identity string of `*` indicates that
all authentication clients are authorized.
type: array
items:
type: string
pattern: '^(\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
identityRefs:
type: array
items:
type: object
required:
- kind
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent. When unspecified,
this refers to all resources of the specified Group
and Kind in the specified namespace.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: networkauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: NetworkAuthentication
plural: networkauthentications
singular: networkauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
NetworkAuthentication defines a list of authenticated client
networks to be referenced by an `AuthorizationPolicy`. If a
client connection originates from ANY of the of the provided
networks, the connection is considered authenticated.
type: object
required: [networks]
properties:
networks:
type: array
items:
type: object
required: [cidr]
properties:
cidr:
description: >-
The CIDR of the network to be authorized.
type: string
except:
description: >-
A list of IP networks/addresses not to be included in
the above `cidr`.
type: array
items:
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: servers.policy.linkerd.io
annotations:
@ -859,6 +1097,9 @@ webhooks:
apiGroups: ["policy.linkerd.io"]
apiVersions: ["v1alpha1", "v1beta1"]
resources:
- authorizationpolicies
- networkauthentications
- meshtlsauthentications
- serverauthorizations
- servers
sideEffects: None
@ -883,6 +1124,9 @@ rules:
- apiGroups:
- policy.linkerd.io
resources:
- authorizationpolicies
- meshtlsauthentications
- networkauthentications
- servers
- serverauthorizations
verbs:

View File

@ -1,6 +1,244 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: authorizationpolicies.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: AuthorizationPolicy
plural: authorizationpolicies
singular: authorizationpolicy
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
Authorizes clients to communicate with Linkerd-proxied server
resources.
type: object
required: [targetRef, requiredAuthenticationRefs]
properties:
targetRef:
description: >-
TargetRef references a resource to which the authorization
policy applies.
type: object
required: [kind, name]
# Modified from the gateway API.
# Copyright 2020 The Kubernetes Authors
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred.
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
requiredAuthenticationRefs:
description: >-
RequiredAuthenticationRefs enumerates a set of required
authentications. ALL authentications must be satisfied for
the authorization to apply. If any of the referred objects
cannot be found, the authorization will be ignored.
type: array
items:
type: object
required: [kind, name]
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: meshtlsauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: MeshTLSAuthentication
plural: meshtlsauthentications
singular: meshtlsauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
MeshTLSAuthentication defines a list of authenticated client IDs
to be referenced by an `AuthorizationPolicy`. If a client
connection has the mutually-authenticated identity that matches
ANY of the of the provided identities, the connection is
considered authenticated.
type: object
oneOf:
- required: [identities]
- required: [identityRefs]
properties:
identities:
description: >-
Authorizes clients with the provided proxy identity strings
(as provided via MTLS)
The `*` prefix can be used to match all identities in
a domain. An identity string of `*` indicates that
all authentication clients are authorized.
type: array
items:
type: string
pattern: '^(\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
identityRefs:
type: array
items:
type: object
required:
- kind
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent. When unspecified,
this refers to all resources of the specified Group
and Kind in the specified namespace.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: networkauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: NetworkAuthentication
plural: networkauthentications
singular: networkauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
NetworkAuthentication defines a list of authenticated client
networks to be referenced by an `AuthorizationPolicy`. If a
client connection originates from ANY of the of the provided
networks, the connection is considered authenticated.
type: object
required: [networks]
properties:
networks:
type: array
items:
type: object
required: [cidr]
properties:
cidr:
description: >-
The CIDR of the network to be authorized.
type: string
except:
description: >-
A list of IP networks/addresses not to be included in
the above `cidr`.
type: array
items:
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: servers.policy.linkerd.io
annotations:
@ -859,6 +1097,9 @@ webhooks:
apiGroups: ["policy.linkerd.io"]
apiVersions: ["v1alpha1", "v1beta1"]
resources:
- authorizationpolicies
- networkauthentications
- meshtlsauthentications
- serverauthorizations
- servers
sideEffects: None
@ -883,6 +1124,9 @@ rules:
- apiGroups:
- policy.linkerd.io
resources:
- authorizationpolicies
- meshtlsauthentications
- networkauthentications
- servers
- serverauthorizations
verbs:

View File

@ -161,6 +161,9 @@ webhooks:
apiGroups: ["policy.linkerd.io"]
apiVersions: ["v1alpha1", "v1beta1"]
resources:
- authorizationpolicies
- networkauthentications
- meshtlsauthentications
- serverauthorizations
- servers
sideEffects: None
@ -185,6 +188,9 @@ rules:
- apiGroups:
- policy.linkerd.io
resources:
- authorizationpolicies
- meshtlsauthentications
- networkauthentications
- servers
- serverauthorizations
verbs:

View File

@ -161,6 +161,9 @@ webhooks:
apiGroups: ["policy.linkerd.io"]
apiVersions: ["v1alpha1", "v1beta1"]
resources:
- authorizationpolicies
- networkauthentications
- meshtlsauthentications
- serverauthorizations
- servers
sideEffects: None
@ -185,6 +188,9 @@ rules:
- apiGroups:
- policy.linkerd.io
resources:
- authorizationpolicies
- meshtlsauthentications
- networkauthentications
- servers
- serverauthorizations
verbs:

View File

@ -1,4 +1,248 @@
---
# Source: linkerd-crds/templates/policy/authorization-policy.yaml
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: authorizationpolicies.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/helm linkerd-version
labels:
helm.sh/chart: linkerd-crds-
linkerd.io/control-plane-ns: linkerd-dev
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: AuthorizationPolicy
plural: authorizationpolicies
singular: authorizationpolicy
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
Authorizes clients to communicate with Linkerd-proxied server
resources.
type: object
required: [targetRef, requiredAuthenticationRefs]
properties:
targetRef:
description: >-
TargetRef references a resource to which the authorization
policy applies.
type: object
required: [kind, name]
# Modified from the gateway API.
# Copyright 2020 The Kubernetes Authors
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred.
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
requiredAuthenticationRefs:
description: >-
RequiredAuthenticationRefs enumerates a set of required
authentications. ALL authentications must be satisfied for
the authorization to apply. If any of the referred objects
cannot be found, the authorization will be ignored.
type: array
items:
type: object
required: [kind, name]
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
# Source: linkerd-crds/templates/policy/meshtls-authentication.yaml
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: meshtlsauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/helm linkerd-version
labels:
helm.sh/chart: linkerd-crds-
linkerd.io/control-plane-ns: linkerd-dev
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: MeshTLSAuthentication
plural: meshtlsauthentications
singular: meshtlsauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
MeshTLSAuthentication defines a list of authenticated client IDs
to be referenced by an `AuthorizationPolicy`. If a client
connection has the mutually-authenticated identity that matches
ANY of the of the provided identities, the connection is
considered authenticated.
type: object
oneOf:
- required: [identities]
- required: [identityRefs]
properties:
identities:
description: >-
Authorizes clients with the provided proxy identity strings
(as provided via MTLS)
The `*` prefix can be used to match all identities in
a domain. An identity string of `*` indicates that
all authentication clients are authorized.
type: array
items:
type: string
pattern: '^(\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
identityRefs:
type: array
items:
type: object
required:
- kind
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent. When unspecified,
this refers to all resources of the specified Group
and Kind in the specified namespace.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
# Source: linkerd-crds/templates/policy/network-authentication.yaml
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: networkauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/helm linkerd-version
labels:
helm.sh/chart: linkerd-crds-
linkerd.io/control-plane-ns: linkerd-dev
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: NetworkAuthentication
plural: networkauthentications
singular: networkauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
NetworkAuthentication defines a list of authenticated client
networks to be referenced by an `AuthorizationPolicy`. If a
client connection originates from ANY of the of the provided
networks, the connection is considered authenticated.
type: object
required: [networks]
properties:
networks:
type: array
items:
type: object
required: [cidr]
properties:
cidr:
description: >-
The CIDR of the network to be authorized.
type: string
except:
description: >-
A list of IP networks/addresses not to be included in
the above `cidr`.
type: array
items:
type: string
---
# Source: linkerd-crds/templates/policy/server.yaml
---
apiVersion: apiextensions.k8s.io/v1

View File

@ -1,4 +1,248 @@
---
# Source: linkerd-crds/templates/policy/authorization-policy.yaml
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: authorizationpolicies.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/helm linkerd-version
labels:
helm.sh/chart: linkerd-crds-
linkerd.io/control-plane-ns: linkerd-dev
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: AuthorizationPolicy
plural: authorizationpolicies
singular: authorizationpolicy
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
Authorizes clients to communicate with Linkerd-proxied server
resources.
type: object
required: [targetRef, requiredAuthenticationRefs]
properties:
targetRef:
description: >-
TargetRef references a resource to which the authorization
policy applies.
type: object
required: [kind, name]
# Modified from the gateway API.
# Copyright 2020 The Kubernetes Authors
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred.
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
requiredAuthenticationRefs:
description: >-
RequiredAuthenticationRefs enumerates a set of required
authentications. ALL authentications must be satisfied for
the authorization to apply. If any of the referred objects
cannot be found, the authorization will be ignored.
type: array
items:
type: object
required: [kind, name]
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
# Source: linkerd-crds/templates/policy/meshtls-authentication.yaml
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: meshtlsauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/helm linkerd-version
labels:
helm.sh/chart: linkerd-crds-
linkerd.io/control-plane-ns: linkerd-dev
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: MeshTLSAuthentication
plural: meshtlsauthentications
singular: meshtlsauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
MeshTLSAuthentication defines a list of authenticated client IDs
to be referenced by an `AuthorizationPolicy`. If a client
connection has the mutually-authenticated identity that matches
ANY of the of the provided identities, the connection is
considered authenticated.
type: object
oneOf:
- required: [identities]
- required: [identityRefs]
properties:
identities:
description: >-
Authorizes clients with the provided proxy identity strings
(as provided via MTLS)
The `*` prefix can be used to match all identities in
a domain. An identity string of `*` indicates that
all authentication clients are authorized.
type: array
items:
type: string
pattern: '^(\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
identityRefs:
type: array
items:
type: object
required:
- kind
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent. When unspecified,
this refers to all resources of the specified Group
and Kind in the specified namespace.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
# Source: linkerd-crds/templates/policy/network-authentication.yaml
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: networkauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/helm linkerd-version
labels:
helm.sh/chart: linkerd-crds-
linkerd.io/control-plane-ns: linkerd-dev
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: NetworkAuthentication
plural: networkauthentications
singular: networkauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
NetworkAuthentication defines a list of authenticated client
networks to be referenced by an `AuthorizationPolicy`. If a
client connection originates from ANY of the of the provided
networks, the connection is considered authenticated.
type: object
required: [networks]
properties:
networks:
type: array
items:
type: object
required: [cidr]
properties:
cidr:
description: >-
The CIDR of the network to be authorized.
type: string
except:
description: >-
A list of IP networks/addresses not to be included in
the above `cidr`.
type: array
items:
type: string
---
# Source: linkerd-crds/templates/policy/server.yaml
---
apiVersion: apiextensions.k8s.io/v1

View File

@ -161,6 +161,9 @@ webhooks:
apiGroups: ["policy.linkerd.io"]
apiVersions: ["v1alpha1", "v1beta1"]
resources:
- authorizationpolicies
- networkauthentications
- meshtlsauthentications
- serverauthorizations
- servers
sideEffects: None
@ -185,6 +188,9 @@ rules:
- apiGroups:
- policy.linkerd.io
resources:
- authorizationpolicies
- meshtlsauthentications
- networkauthentications
- servers
- serverauthorizations
verbs:

View File

@ -161,6 +161,9 @@ webhooks:
apiGroups: ["policy.linkerd.io"]
apiVersions: ["v1alpha1", "v1beta1"]
resources:
- authorizationpolicies
- networkauthentications
- meshtlsauthentications
- serverauthorizations
- servers
sideEffects: None
@ -185,6 +188,9 @@ rules:
- apiGroups:
- policy.linkerd.io
resources:
- authorizationpolicies
- meshtlsauthentications
- networkauthentications
- servers
- serverauthorizations
verbs:

View File

@ -1,6 +1,244 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: authorizationpolicies.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: AuthorizationPolicy
plural: authorizationpolicies
singular: authorizationpolicy
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
Authorizes clients to communicate with Linkerd-proxied server
resources.
type: object
required: [targetRef, requiredAuthenticationRefs]
properties:
targetRef:
description: >-
TargetRef references a resource to which the authorization
policy applies.
type: object
required: [kind, name]
# Modified from the gateway API.
# Copyright 2020 The Kubernetes Authors
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred.
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
requiredAuthenticationRefs:
description: >-
RequiredAuthenticationRefs enumerates a set of required
authentications. ALL authentications must be satisfied for
the authorization to apply. If any of the referred objects
cannot be found, the authorization will be ignored.
type: array
items:
type: object
required: [kind, name]
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: meshtlsauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: MeshTLSAuthentication
plural: meshtlsauthentications
singular: meshtlsauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
MeshTLSAuthentication defines a list of authenticated client IDs
to be referenced by an `AuthorizationPolicy`. If a client
connection has the mutually-authenticated identity that matches
ANY of the of the provided identities, the connection is
considered authenticated.
type: object
oneOf:
- required: [identities]
- required: [identityRefs]
properties:
identities:
description: >-
Authorizes clients with the provided proxy identity strings
(as provided via MTLS)
The `*` prefix can be used to match all identities in
a domain. An identity string of `*` indicates that
all authentication clients are authorized.
type: array
items:
type: string
pattern: '^(\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
identityRefs:
type: array
items:
type: object
required:
- kind
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent. When unspecified,
this refers to all resources of the specified Group
and Kind in the specified namespace.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: networkauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: NetworkAuthentication
plural: networkauthentications
singular: networkauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
NetworkAuthentication defines a list of authenticated client
networks to be referenced by an `AuthorizationPolicy`. If a
client connection originates from ANY of the of the provided
networks, the connection is considered authenticated.
type: object
required: [networks]
properties:
networks:
type: array
items:
type: object
required: [cidr]
properties:
cidr:
description: >-
The CIDR of the network to be authorized.
type: string
except:
description: >-
A list of IP networks/addresses not to be included in
the above `cidr`.
type: array
items:
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: servers.policy.linkerd.io
annotations:
@ -859,6 +1097,9 @@ webhooks:
apiGroups: ["policy.linkerd.io"]
apiVersions: ["v1alpha1", "v1beta1"]
resources:
- authorizationpolicies
- networkauthentications
- meshtlsauthentications
- serverauthorizations
- servers
sideEffects: None
@ -883,6 +1124,9 @@ rules:
- apiGroups:
- policy.linkerd.io
resources:
- authorizationpolicies
- meshtlsauthentications
- networkauthentications
- servers
- serverauthorizations
verbs:

View File

@ -1,6 +1,244 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: authorizationpolicies.policy.linkerd.io
annotations:
linkerd.io/created-by: CliVersion
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: AuthorizationPolicy
plural: authorizationpolicies
singular: authorizationpolicy
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
Authorizes clients to communicate with Linkerd-proxied server
resources.
type: object
required: [targetRef, requiredAuthenticationRefs]
properties:
targetRef:
description: >-
TargetRef references a resource to which the authorization
policy applies.
type: object
required: [kind, name]
# Modified from the gateway API.
# Copyright 2020 The Kubernetes Authors
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred.
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
requiredAuthenticationRefs:
description: >-
RequiredAuthenticationRefs enumerates a set of required
authentications. ALL authentications must be satisfied for
the authorization to apply. If any of the referred objects
cannot be found, the authorization will be ignored.
type: array
items:
type: object
required: [kind, name]
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: meshtlsauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: CliVersion
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: MeshTLSAuthentication
plural: meshtlsauthentications
singular: meshtlsauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
MeshTLSAuthentication defines a list of authenticated client IDs
to be referenced by an `AuthorizationPolicy`. If a client
connection has the mutually-authenticated identity that matches
ANY of the of the provided identities, the connection is
considered authenticated.
type: object
oneOf:
- required: [identities]
- required: [identityRefs]
properties:
identities:
description: >-
Authorizes clients with the provided proxy identity strings
(as provided via MTLS)
The `*` prefix can be used to match all identities in
a domain. An identity string of `*` indicates that
all authentication clients are authorized.
type: array
items:
type: string
pattern: '^(\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
identityRefs:
type: array
items:
type: object
required:
- kind
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent. When unspecified,
this refers to all resources of the specified Group
and Kind in the specified namespace.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: networkauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: CliVersion
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: NetworkAuthentication
plural: networkauthentications
singular: networkauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
NetworkAuthentication defines a list of authenticated client
networks to be referenced by an `AuthorizationPolicy`. If a
client connection originates from ANY of the of the provided
networks, the connection is considered authenticated.
type: object
required: [networks]
properties:
networks:
type: array
items:
type: object
required: [cidr]
properties:
cidr:
description: >-
The CIDR of the network to be authorized.
type: string
except:
description: >-
A list of IP networks/addresses not to be included in
the above `cidr`.
type: array
items:
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: servers.policy.linkerd.io
annotations:
@ -856,6 +1094,9 @@ webhooks:
apiGroups: ["policy.linkerd.io"]
apiVersions: ["v1alpha1", "v1beta1"]
resources:
- authorizationpolicies
- networkauthentications
- meshtlsauthentications
- serverauthorizations
- servers
sideEffects: None
@ -880,6 +1121,9 @@ rules:
- apiGroups:
- policy.linkerd.io
resources:
- authorizationpolicies
- meshtlsauthentications
- networkauthentications
- servers
- serverauthorizations
verbs:

View File

@ -1,6 +1,244 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: authorizationpolicies.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: AuthorizationPolicy
plural: authorizationpolicies
singular: authorizationpolicy
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
Authorizes clients to communicate with Linkerd-proxied server
resources.
type: object
required: [targetRef, requiredAuthenticationRefs]
properties:
targetRef:
description: >-
TargetRef references a resource to which the authorization
policy applies.
type: object
required: [kind, name]
# Modified from the gateway API.
# Copyright 2020 The Kubernetes Authors
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred.
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
requiredAuthenticationRefs:
description: >-
RequiredAuthenticationRefs enumerates a set of required
authentications. ALL authentications must be satisfied for
the authorization to apply. If any of the referred objects
cannot be found, the authorization will be ignored.
type: array
items:
type: object
required: [kind, name]
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: meshtlsauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: MeshTLSAuthentication
plural: meshtlsauthentications
singular: meshtlsauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
MeshTLSAuthentication defines a list of authenticated client IDs
to be referenced by an `AuthorizationPolicy`. If a client
connection has the mutually-authenticated identity that matches
ANY of the of the provided identities, the connection is
considered authenticated.
type: object
oneOf:
- required: [identities]
- required: [identityRefs]
properties:
identities:
description: >-
Authorizes clients with the provided proxy identity strings
(as provided via MTLS)
The `*` prefix can be used to match all identities in
a domain. An identity string of `*` indicates that
all authentication clients are authorized.
type: array
items:
type: string
pattern: '^(\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
identityRefs:
type: array
items:
type: object
required:
- kind
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent. When unspecified,
this refers to all resources of the specified Group
and Kind in the specified namespace.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: networkauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: NetworkAuthentication
plural: networkauthentications
singular: networkauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
NetworkAuthentication defines a list of authenticated client
networks to be referenced by an `AuthorizationPolicy`. If a
client connection originates from ANY of the of the provided
networks, the connection is considered authenticated.
type: object
required: [networks]
properties:
networks:
type: array
items:
type: object
required: [cidr]
properties:
cidr:
description: >-
The CIDR of the network to be authorized.
type: string
except:
description: >-
A list of IP networks/addresses not to be included in
the above `cidr`.
type: array
items:
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: servers.policy.linkerd.io
annotations:
@ -859,6 +1097,9 @@ webhooks:
apiGroups: ["policy.linkerd.io"]
apiVersions: ["v1alpha1", "v1beta1"]
resources:
- authorizationpolicies
- networkauthentications
- meshtlsauthentications
- serverauthorizations
- servers
sideEffects: None
@ -883,6 +1124,9 @@ rules:
- apiGroups:
- policy.linkerd.io
resources:
- authorizationpolicies
- meshtlsauthentications
- networkauthentications
- servers
- serverauthorizations
verbs:

View File

@ -1,6 +1,244 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: authorizationpolicies.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: AuthorizationPolicy
plural: authorizationpolicies
singular: authorizationpolicy
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
Authorizes clients to communicate with Linkerd-proxied server
resources.
type: object
required: [targetRef, requiredAuthenticationRefs]
properties:
targetRef:
description: >-
TargetRef references a resource to which the authorization
policy applies.
type: object
required: [kind, name]
# Modified from the gateway API.
# Copyright 2020 The Kubernetes Authors
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred.
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
requiredAuthenticationRefs:
description: >-
RequiredAuthenticationRefs enumerates a set of required
authentications. ALL authentications must be satisfied for
the authorization to apply. If any of the referred objects
cannot be found, the authorization will be ignored.
type: array
items:
type: object
required: [kind, name]
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: meshtlsauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: MeshTLSAuthentication
plural: meshtlsauthentications
singular: meshtlsauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
MeshTLSAuthentication defines a list of authenticated client IDs
to be referenced by an `AuthorizationPolicy`. If a client
connection has the mutually-authenticated identity that matches
ANY of the of the provided identities, the connection is
considered authenticated.
type: object
oneOf:
- required: [identities]
- required: [identityRefs]
properties:
identities:
description: >-
Authorizes clients with the provided proxy identity strings
(as provided via MTLS)
The `*` prefix can be used to match all identities in
a domain. An identity string of `*` indicates that
all authentication clients are authorized.
type: array
items:
type: string
pattern: '^(\*|[a-z0-9]([-a-z0-9]*[a-z0-9])?)(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'
identityRefs:
type: array
items:
type: object
required:
- kind
properties:
group:
description: >-
Group is the group of the referent. When empty, the
Kubernetes core API group is inferred."
maxLength: 253
pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
type: string
kind:
description: >-
Kind is the kind of the referent.
maxLength: 63
minLength: 1
pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$
type: string
name:
description: >-
Name is the name of the referent. When unspecified,
this refers to all resources of the specified Group
and Kind in the specified namespace.
maxLength: 253
minLength: 1
type: string
namespace:
description: >-
Name is the name of the referent. When unspecified,
this authentication refers to the local namespace.
maxLength: 253
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: networkauthentications.policy.linkerd.io
annotations:
linkerd.io/created-by: linkerd/cli dev-undefined
labels:
helm.sh/chart: linkerd-control-plane-1.1.11-edge
linkerd.io/control-plane-ns: linkerd
spec:
group: policy.linkerd.io
scope: Namespaced
names:
kind: NetworkAuthentication
plural: networkauthentications
singular: networkauthentication
shortNames: []
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: [spec]
properties:
spec:
description: >-
NetworkAuthentication defines a list of authenticated client
networks to be referenced by an `AuthorizationPolicy`. If a
client connection originates from ANY of the of the provided
networks, the connection is considered authenticated.
type: object
required: [networks]
properties:
networks:
type: array
items:
type: object
required: [cidr]
properties:
cidr:
description: >-
The CIDR of the network to be authorized.
type: string
except:
description: >-
A list of IP networks/addresses not to be included in
the above `cidr`.
type: array
items:
type: string
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: servers.policy.linkerd.io
annotations:
@ -859,6 +1097,9 @@ webhooks:
apiGroups: ["policy.linkerd.io"]
apiVersions: ["v1alpha1", "v1beta1"]
resources:
- authorizationpolicies
- networkauthentications
- meshtlsauthentications
- serverauthorizations
- servers
sideEffects: None
@ -883,6 +1124,9 @@ rules:
- apiGroups:
- policy.linkerd.io
resources:
- authorizationpolicies
- meshtlsauthentications
- networkauthentications
- servers
- serverauthorizations
verbs:

View File

@ -24,9 +24,23 @@ pub type InboundServerStream = Pin<Box<dyn Stream<Item = InboundServer> + Send +
/// Inbound server configuration.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct InboundServer {
pub name: String,
pub reference: ServerRef,
pub protocol: ProxyProtocol,
pub authorizations: HashMap<String, ClientAuthorization>,
pub authorizations: HashMap<AuthorizationRef, ClientAuthorization>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ServerRef {
Default(String),
Server(String),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum AuthorizationRef {
Default(String),
ServerAuthorization(String),
AuthorizationPolicy(String),
}
/// Describes how a proxy should handle inbound connections.
@ -63,7 +77,7 @@ pub enum ClientAuthentication {
/// Indicates that clients need not be authenticated.
Unauthenticated,
/// Indicates that clients must use TLS bu need not provide a client identity.
/// Indicates that clients must use TLS but need not provide a client identity.
TlsUnauthenticated,
/// Indicates that clients must use mutually-authenticated TLS.

View File

@ -15,6 +15,7 @@ drain = "0.1"
futures = { version = "0.3", default-features = false }
linkerd2-proxy-api = { version = "0.3", features = ["inbound", "server"] }
linkerd-policy-controller-core = { path = "../core" }
maplit = "1"
tokio = { version = "1", features = ["macros"] }
tonic = { version = "0.6", default-features = false, features = ["transport"] }
tracing = "0.1"

View File

@ -7,9 +7,11 @@ use linkerd2_proxy_api::inbound::{
inbound_server_policies_server::{InboundServerPolicies, InboundServerPoliciesServer},
};
use linkerd_policy_controller_core::{
ClientAuthentication, ClientAuthorization, DiscoverInboundServer, IdentityMatch, InboundServer,
InboundServerStream, IpNet, NetworkMatch, ProxyProtocol,
AuthorizationRef, ClientAuthentication, ClientAuthorization, DiscoverInboundServer,
IdentityMatch, InboundServer, InboundServerStream, IpNet, NetworkMatch, ProxyProtocol,
ServerRef,
};
use maplit::*;
use std::sync::Arc;
use tracing::trace;
@ -194,9 +196,16 @@ fn to_server(srv: &InboundServer, cluster_networks: &[IpNet]) -> proto::Server {
.collect();
trace!(?authorizations);
let labels = vec![("name".to_string(), srv.name.to_string())]
.into_iter()
.collect();
let labels = match &srv.reference {
ServerRef::Default(name) => convert_args!(hashmap!(
"kind" => "default",
"name" => name,
)),
ServerRef::Server(name) => convert_args!(hashmap!(
"kind" => "server",
"name" => name,
)),
};
trace!(?labels);
proto::Server {
@ -208,7 +217,7 @@ fn to_server(srv: &InboundServer, cluster_networks: &[IpNet]) -> proto::Server {
}
fn to_authz(
name: impl ToString,
reference: &AuthorizationRef,
ClientAuthorization {
networks,
authentication,
@ -233,9 +242,20 @@ fn to_authz(
.collect()
};
let labels = vec![("name".to_string(), name.to_string())]
.into_iter()
.collect();
let labels = match reference {
AuthorizationRef::Default(name) => convert_args!(hashmap!(
"kind" => "default",
"name" => name,
)),
AuthorizationRef::ServerAuthorization(name) => convert_args!(hashmap!(
"kind" => "serverauthorization",
"name" => name,
)),
AuthorizationRef::AuthorizationPolicy(name) => convert_args!(hashmap!(
"kind" => "authorizationpolicy",
"name" => name,
)),
};
let authn = match authentication {
ClientAuthentication::Unauthenticated => proto::Authn {

View File

@ -7,7 +7,9 @@ pub mod policy;
pub use self::labels::Labels;
pub use k8s_openapi::api::{
self,
core::v1::{Namespace, Node, NodeSpec, Pod, PodSpec, PodStatus},
core::v1::{Namespace, Node, NodeSpec, Pod, PodSpec, PodStatus, ServiceAccount},
};
pub use kube::{
api::{ObjectMeta, Resource, ResourceExt},
runtime::watcher::Event as WatchEvent,
};
pub use kube::api::{ObjectMeta, Resource, ResourceExt};
pub use kube::runtime::watcher::Event as WatchEvent;

View File

@ -1,9 +1,17 @@
pub mod network;
pub mod authorization_policy;
pub mod meshtls_authentication;
mod network;
pub mod network_authentication;
pub mod server;
pub mod server_authorization;
pub mod target_ref;
pub use self::{
authorization_policy::{AuthorizationPolicy, AuthorizationPolicySpec},
meshtls_authentication::{MeshTLSAuthentication, MeshTLSAuthenticationSpec},
network::Network,
network_authentication::{NetworkAuthentication, NetworkAuthenticationSpec},
server::{Server, ServerSpec},
server_authorization::{ServerAuthorization, ServerAuthorizationSpec},
target_ref::{ClusterTargetRef, LocalTargetRef, NamespacedTargetRef},
};

View File

@ -0,0 +1,22 @@
use super::{LocalTargetRef, NamespacedTargetRef};
#[derive(
Clone,
Debug,
Default,
kube::CustomResource,
serde::Deserialize,
serde::Serialize,
schemars::JsonSchema,
)]
#[kube(
group = "policy.linkerd.io",
version = "v1alpha1",
kind = "AuthorizationPolicy",
namespaced
)]
#[serde(rename_all = "camelCase")]
pub struct AuthorizationPolicySpec {
pub target_ref: LocalTargetRef,
pub required_authentication_refs: Vec<NamespacedTargetRef>,
}

View File

@ -0,0 +1,23 @@
use super::NamespacedTargetRef;
#[derive(
Clone,
Debug,
Default,
PartialEq,
kube::CustomResource,
serde::Deserialize,
serde::Serialize,
schemars::JsonSchema,
)]
#[kube(
group = "policy.linkerd.io",
version = "v1alpha1",
kind = "MeshTLSAuthentication",
namespaced
)]
#[serde(rename_all = "camelCase")]
pub struct MeshTLSAuthenticationSpec {
pub identities: Option<Vec<String>>,
pub identity_refs: Option<Vec<NamespacedTargetRef>>,
}

View File

@ -0,0 +1,22 @@
pub use super::Network;
#[derive(
Clone,
Debug,
Default,
PartialEq,
kube::CustomResource,
serde::Deserialize,
serde::Serialize,
schemars::JsonSchema,
)]
#[kube(
group = "policy.linkerd.io",
version = "v1alpha1",
kind = "NetworkAuthentication",
namespaced
)]
#[serde(rename_all = "camelCase")]
pub struct NetworkAuthenticationSpec {
pub networks: Vec<Network>,
}

View File

@ -0,0 +1,351 @@
#[derive(
Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize, schemars::JsonSchema,
)]
pub struct ClusterTargetRef {
pub group: Option<String>,
pub kind: String,
pub name: String,
}
#[derive(
Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize, schemars::JsonSchema,
)]
pub struct LocalTargetRef {
pub group: Option<String>,
pub kind: String,
pub name: String,
}
#[derive(
Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize, schemars::JsonSchema,
)]
pub struct NamespacedTargetRef {
pub group: Option<String>,
pub kind: String,
pub name: String,
pub namespace: Option<String>,
}
impl ClusterTargetRef {
pub fn from_resource<T>(resource: &T) -> Self
where
T: kube::Resource,
T::DynamicType: Default,
{
let (group, kind, name) = group_kind_name(resource);
Self { group, kind, name }
}
/// Returns the target ref kind, qualified by its group, if necessary.
pub fn canonical_kind(&self) -> String {
canonical_kind(self.group.as_deref(), &self.kind)
}
/// Checks whether the target references the given resource type
pub fn targets_kind<T>(&self) -> bool
where
T: kube::Resource,
T::DynamicType: Default,
{
targets_kind::<T>(self.group.as_deref(), &self.kind)
}
/// Checks whether the target references the given cluster-level resource
pub fn targets<T>(&self, resource: &T) -> bool
where
T: kube::Resource,
T::DynamicType: Default,
{
if !self.targets_kind::<T>() {
return false;
}
if resource.meta().namespace.is_some() {
// If the reference or the resource has a namespace, that's a deal-breaker.
return false;
}
match resource.meta().name.as_deref() {
None => return false,
Some(rname) => {
if !self.name.eq_ignore_ascii_case(rname) {
return false;
}
}
}
true
}
}
impl LocalTargetRef {
pub fn from_resource<T>(resource: &T) -> Self
where
T: kube::Resource,
T::DynamicType: Default,
{
let (group, kind, name) = group_kind_name(resource);
Self { group, kind, name }
}
/// Returns the target ref kind, qualified by its group, if necessary.
pub fn canonical_kind(&self) -> String {
canonical_kind(self.group.as_deref(), &self.kind)
}
/// Checks whether the target references the given resource type
pub fn targets_kind<T>(&self) -> bool
where
T: kube::Resource,
T::DynamicType: Default,
{
targets_kind::<T>(self.group.as_deref(), &self.kind)
}
/// Checks whether the target references the given namespaced resource
pub fn targets<T>(&self, resource: &T, local_ns: &str) -> bool
where
T: kube::Resource,
T::DynamicType: Default,
{
if !self.targets_kind::<T>() {
return false;
}
// If the resource specifies a namespace other than the target or the
// default namespace, that's a deal-breaker.
match resource.meta().namespace.as_deref() {
Some(rns) if rns.eq_ignore_ascii_case(local_ns) => {}
_ => return false,
};
match resource.meta().name.as_deref() {
Some(rname) => rname.eq_ignore_ascii_case(&self.name),
_ => false,
}
}
}
impl NamespacedTargetRef {
pub fn from_resource<T>(resource: &T) -> Self
where
T: kube::Resource,
T::DynamicType: Default,
{
let (group, kind, name) = group_kind_name(resource);
let namespace = resource.meta().namespace.clone();
Self {
group,
kind,
name,
namespace,
}
}
/// Returns the target ref kind, qualified by its group, if necessary.
pub fn canonical_kind(&self) -> String {
canonical_kind(self.group.as_deref(), &self.kind)
}
/// Checks whether the target references the given resource type
pub fn targets_kind<T>(&self) -> bool
where
T: kube::Resource,
T::DynamicType: Default,
{
targets_kind::<T>(self.group.as_deref(), &self.kind)
}
/// Checks whether the target references the given namespaced resource
pub fn targets<T>(&self, resource: &T, local_ns: &str) -> bool
where
T: kube::Resource,
T::DynamicType: Default,
{
if !self.targets_kind::<T>() {
return false;
}
// If the resource specifies a namespace other than the target or the
// default namespace, that's a deal-breaker.
let tns = self.namespace.as_deref().unwrap_or(local_ns);
match resource.meta().namespace.as_deref() {
Some(rns) if rns.eq_ignore_ascii_case(tns) => {}
_ => return false,
};
match resource.meta().name.as_deref() {
None => return false,
Some(rname) => {
if !self.name.eq_ignore_ascii_case(rname) {
return false;
}
}
}
true
}
}
fn canonical_kind(group: Option<&str>, kind: &str) -> String {
if let Some(group) = group {
format!("{}.{}", kind, group)
} else {
kind.to_string()
}
}
fn targets_kind<T>(group: Option<&str>, kind: &str) -> bool
where
T: kube::Resource,
T::DynamicType: Default,
{
let dt = Default::default();
let mut t_group = &*T::group(&dt);
if t_group.is_empty() {
t_group = "core";
}
group.unwrap_or("core").eq_ignore_ascii_case(t_group)
&& kind.eq_ignore_ascii_case(&*T::kind(&dt))
}
fn group_kind_name<T>(resource: &T) -> (Option<String>, String, String)
where
T: kube::Resource,
T::DynamicType: Default,
{
let dt = Default::default();
let group = match T::group(&dt) {
g if (*g).is_empty() => None,
g => Some(g.to_string()),
};
let kind = T::kind(&dt).to_string();
let name = resource
.meta()
.name
.clone()
.expect("resource must have a name");
(group, kind, name)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{policy::Server, Namespace, ObjectMeta, ServiceAccount};
#[test]
fn cluster_targets_namespace() {
let t = ClusterTargetRef {
kind: "Namespace".to_string(),
name: "appns".to_string(),
..Default::default()
};
assert!(t.targets_kind::<Namespace>());
assert!(t.targets(&Namespace {
metadata: ObjectMeta {
name: Some("appns".to_string()),
..ObjectMeta::default()
},
..Namespace::default()
}));
}
#[test]
fn namespaced_targets_service_account() {
for tgt in &[
NamespacedTargetRef {
kind: "ServiceAccount".to_string(),
name: "default".to_string(),
namespace: Some("appns".to_string()),
..Default::default()
},
NamespacedTargetRef {
group: Some("core".to_string()),
kind: "ServiceAccount".to_string(),
name: "default".to_string(),
namespace: Some("appns".to_string()),
},
NamespacedTargetRef {
group: Some("CORE".to_string()),
kind: "SERVICEACCOUNT".to_string(),
name: "DEFAULT".to_string(),
namespace: Some("APPNS".to_string()),
},
NamespacedTargetRef {
kind: "ServiceAccount".to_string(),
name: "default".to_string(),
..Default::default()
},
] {
assert!(tgt.targets_kind::<ServiceAccount>());
assert!(!tgt.targets_kind::<Namespace>());
let sa = ServiceAccount {
metadata: ObjectMeta {
namespace: Some("appns".to_string()),
name: Some("default".to_string()),
..ObjectMeta::default()
},
..ServiceAccount::default()
};
assert!(
tgt.targets(&sa, "appns"),
"ServiceAccounts are targeted by name: {:#?}",
tgt
);
let sa = ServiceAccount {
metadata: ObjectMeta {
namespace: Some("otherns".to_string()),
name: Some("default".to_string()),
..ObjectMeta::default()
},
..ServiceAccount::default()
};
assert!(
!tgt.targets(&sa, "appns"),
"ServiceAccounts in other namespaces should not be targeted: {:#?}",
tgt
);
}
let tgt = NamespacedTargetRef {
kind: "ServiceAccount".to_string(),
name: "default".to_string(),
..Default::default()
};
assert!(
{
let sa = ServiceAccount {
metadata: ObjectMeta {
namespace: Some("appns".to_string()),
name: Some("special".to_string()),
..ObjectMeta::default()
},
..ServiceAccount::default()
};
!tgt.targets(&sa, "appns")
},
"resource comparison uses "
);
}
#[test]
fn namespaced_targets_server() {
let tgt = NamespacedTargetRef {
group: Some("policy.linkerd.io".to_string()),
kind: "Server".to_string(),
name: "http".to_string(),
namespace: Some("appns".to_string()),
};
assert!(tgt.targets_kind::<Server>());
}
}

View File

@ -17,6 +17,7 @@ tokio = { version = "1", features = ["macros", "rt", "sync"] }
tracing = "0.1"
[dev-dependencies]
maplit = "1"
tokio-stream = "0.1"
tokio-test = "0.4"
tracing-subscriber = "0.3"

View File

@ -0,0 +1,85 @@
use anyhow::{bail, Result};
use linkerd_policy_controller_k8s_api::{
self as k8s,
policy::{LocalTargetRef, NamespacedTargetRef},
};
#[derive(Debug, PartialEq)]
pub(crate) struct Spec {
pub target: Target,
pub authentications: Vec<AuthenticationTarget>,
}
#[derive(Debug, PartialEq)]
pub(crate) enum Target {
Server(String),
}
#[derive(Debug, PartialEq)]
pub(crate) enum AuthenticationTarget {
MeshTLS {
namespace: Option<String>,
name: String,
},
Network {
namespace: Option<String>,
name: String,
},
}
impl TryFrom<k8s::policy::AuthorizationPolicySpec> for Spec {
type Error = anyhow::Error;
fn try_from(ap: k8s::policy::AuthorizationPolicySpec) -> Result<Self> {
let target = target(ap.target_ref)?;
let authentications = ap
.required_authentication_refs
.into_iter()
.map(authentication_ref)
.collect::<Result<Vec<_>>>()?;
if authentications.is_empty() {
bail!("No authentication targets");
}
Ok(Self {
target,
authentications,
})
}
}
impl Target {
pub(crate) fn server(&self) -> Option<&str> {
match self {
Self::Server(s) => Some(s),
}
}
}
fn target(t: LocalTargetRef) -> Result<Target> {
if t.targets_kind::<k8s::policy::Server>() {
return Ok(Target::Server(t.name));
}
anyhow::bail!(
"unsupported authorization target type: {}",
t.canonical_kind()
);
}
fn authentication_ref(t: NamespacedTargetRef) -> Result<AuthenticationTarget> {
if t.targets_kind::<k8s::policy::MeshTLSAuthentication>() {
Ok(AuthenticationTarget::MeshTLS {
namespace: t.namespace.map(Into::into),
name: t.name,
})
} else if t.targets_kind::<k8s::policy::NetworkAuthentication>() {
Ok(AuthenticationTarget::Network {
namespace: t.namespace.map(Into::into),
name: t.name,
})
} else {
anyhow::bail!("unsupported authentication target: {}", t.canonical_kind());
}
}

View File

@ -6,12 +6,15 @@
//! implements `kubert::index::IndexNamespacedResource` for the indexed
//! kubernetes resources.
use crate::{defaults::DefaultPolicy, pod, server, server_authorization, ClusterInfo};
use crate::{
authorization_policy, defaults::DefaultPolicy, meshtls_authentication, network_authentication,
pod, server, server_authorization, ClusterInfo,
};
use ahash::{AHashMap as HashMap, AHashSet as HashSet};
use anyhow::{bail, Result};
use linkerd_policy_controller_core::{
ClientAuthentication, ClientAuthorization, IdentityMatch, InboundServer, Ipv4Net, Ipv6Net,
ProxyProtocol,
AuthorizationRef, ClientAuthentication, ClientAuthorization, IdentityMatch, InboundServer,
IpNet, Ipv4Net, Ipv6Net, NetworkMatch, ProxyProtocol, ServerRef,
};
use linkerd_policy_controller_k8s_api::{self as k8s, policy::server::Port, ResourceExt};
use parking_lot::RwLock;
@ -28,6 +31,7 @@ pub type SharedIndex = Arc<RwLock<Index>>;
pub struct Index {
cluster_info: Arc<ClusterInfo>,
namespaces: NamespaceIndex,
authentications: AuthenticationNsIndex,
}
/// Holds all `Pod`, `Server`, and `ServerAuthorization` indices by-namespace.
@ -37,6 +41,15 @@ struct NamespaceIndex {
by_ns: HashMap<String, Namespace>,
}
/// Holds all `NetworkAuthentication` and `MeshTLSAuthentication` indices by-namespace.
///
/// This is separate from `NamespaceIndex` because authorization policies may reference
/// authentication resources across namespaces.
#[derive(Debug, Default)]
struct AuthenticationNsIndex {
by_ns: HashMap<String, AuthenticationIndex>,
}
/// Holds `Pod`, `Server`, and `ServerAuthorization` indices for a single namespace.
#[derive(Debug)]
struct Namespace {
@ -45,7 +58,7 @@ struct Namespace {
}
/// Holds all pod data for a single namespace.
#[derive(Debug, Default)]
#[derive(Debug)]
struct PodIndex {
namespace: String,
by_name: HashMap<String, Pod>,
@ -90,9 +103,24 @@ struct PodPortServer {
/// Holds the state of policy resources for a single namespace.
#[derive(Debug)]
struct PolicyIndex {
namespace: String,
cluster_info: Arc<ClusterInfo>,
servers: HashMap<String, server::Server>,
server_authorizations: HashMap<String, server_authorization::ServerAuthz>,
authorization_policies: HashMap<String, authorization_policy::Spec>,
}
#[derive(Debug, Default)]
struct AuthenticationIndex {
meshtls: HashMap<String, meshtls_authentication::Spec>,
network: HashMap<String, network_authentication::Spec>,
}
struct NsUpdate<T> {
added: Vec<(String, T)>,
removed: HashSet<String>,
}
// === impl Index ===
@ -104,8 +132,9 @@ impl Index {
cluster_info: cluster_info.clone(),
namespaces: NamespaceIndex {
cluster_info,
by_ns: HashMap::new(),
by_ns: HashMap::default(),
},
authentications: AuthenticationNsIndex::default(),
}))
}
@ -134,13 +163,34 @@ impl Index {
.rx
.clone())
}
fn ns_with_reindex(&mut self, namespace: String, f: impl FnOnce(&mut Namespace) -> bool) {
self.namespaces
.get_with_reindex(namespace, &self.authentications, f)
}
fn ns_or_default_with_reindex(
&mut self,
namespace: String,
f: impl FnOnce(&mut Namespace) -> bool,
) {
self.namespaces
.get_or_default_with_reindex(namespace, &self.authentications, f)
}
fn reindex_all(&mut self) {
tracing::debug!("Reindexing all namespaces");
for ns in self.namespaces.by_ns.values_mut() {
ns.reindex(&self.authentications);
}
}
}
impl kubert::index::IndexNamespacedResource<k8s::Pod> for Index {
fn apply(&mut self, pod: k8s::Pod) {
let namespace = pod.namespace().unwrap();
let name = pod.name();
let _span = info_span!("apply", ns = %namespace, pod = %name).entered();
let _span = info_span!("apply", ns = %namespace, %name).entered();
let port_names = pod::tcp_port_names(pod.spec);
let meta = pod::Meta::from_metadata(pod.metadata);
@ -151,21 +201,20 @@ impl kubert::index::IndexNamespacedResource<k8s::Pod> for Index {
let ns = self.namespaces.get_or_default(namespace);
match ns.pods.update(name, meta, port_names) {
Ok(None) => {}
Ok(Some(pod)) => pod.reindex_servers(&ns.policy),
Ok(Some(pod)) => pod.reindex_servers(&ns.policy, &self.authentications),
Err(error) => {
tracing::error!(%error, "Illegal pod update");
}
}
}
fn delete(&mut self, ns: String, pod: String) {
let _span = info_span!("delete", %ns, %pod).entered();
fn delete(&mut self, ns: String, name: String) {
tracing::debug!(%ns, %name, "delete");
if let Entry::Occupied(mut ns) = self.namespaces.by_ns.entry(ns) {
// Once the pod is removed, there's nothing else to update. Any open
// watches will complete. No other parts of the index need to be
// updated.
if ns.get_mut().pods.by_name.remove(&pod).is_some() && ns.get().is_empty() {
if ns.get_mut().pods.by_name.remove(&name).is_some() && ns.get().is_empty() {
ns.remove();
}
}
@ -179,30 +228,23 @@ impl kubert::index::IndexNamespacedResource<k8s::policy::Server> for Index {
fn apply(&mut self, srv: k8s::policy::Server) {
let ns = srv.namespace().expect("server must be namespaced");
let name = srv.name();
let _span = info_span!("apply", %ns, srv = %name).entered();
let _span = info_span!("apply", %ns, %name).entered();
let server = server::Server::from_resource(srv, &self.cluster_info);
self.namespaces
.get_or_default_with_reindex(ns, |ns| ns.policy.update_server(name, server))
self.ns_or_default_with_reindex(ns, |ns| ns.policy.update_server(name, server))
}
fn delete(&mut self, ns: String, srv: String) {
let _span = info_span!("delete", %ns, %srv).entered();
self.namespaces
.get_with_reindex(ns, |ns| ns.policy.servers.remove(&srv).is_some())
fn delete(&mut self, ns: String, name: String) {
let _span = info_span!("delete", %ns, %name).entered();
self.ns_with_reindex(ns, |ns| ns.policy.servers.remove(&name).is_some())
}
fn reset(&mut self, srvs: Vec<k8s::policy::Server>, deleted: HashMap<String, HashSet<String>>) {
let _span = info_span!("reset").entered();
#[derive(Default)]
struct Ns {
added: Vec<(String, server::Server)>,
removed: HashSet<String>,
}
// Aggregate all of the updates by namespace so that we only reindex
// once per namespace.
type Ns = NsUpdate<server::Server>;
let mut updates_by_ns = HashMap::<String, Ns>::default();
for srv in srvs.into_iter() {
let namespace = srv.namespace().expect("server must be namespaced");
@ -224,7 +266,7 @@ impl kubert::index::IndexNamespacedResource<k8s::policy::Server> for Index {
// want to create a default namespace instance, we just want to
// clear out all resources for the namespace (and then drop the
// whole namespace, if necessary).
self.namespaces.get_with_reindex(namespace, |ns| {
self.ns_with_reindex(namespace, |ns| {
ns.policy.servers.clear();
true
});
@ -232,8 +274,7 @@ impl kubert::index::IndexNamespacedResource<k8s::policy::Server> for Index {
// Otherwise, we take greater care to reindex only when the
// state actually changed. The vast majority of resets will see
// no actual data change.
self.namespaces
.get_or_default_with_reindex(namespace, |ns| {
self.ns_or_default_with_reindex(namespace, |ns| {
let mut changed = !removed.is_empty();
for name in removed.into_iter() {
ns.policy.servers.remove(&name);
@ -252,20 +293,20 @@ impl kubert::index::IndexNamespacedResource<k8s::policy::ServerAuthorization> fo
fn apply(&mut self, saz: k8s::policy::ServerAuthorization) {
let ns = saz.namespace().unwrap();
let name = saz.name();
let _span = info_span!("apply", %ns, saz = %name).entered();
let _span = info_span!("apply", %ns, %name).entered();
match server_authorization::ServerAuthz::from_resource(saz, &self.cluster_info) {
Ok(meta) => self.namespaces.get_or_default_with_reindex(ns, move |ns| {
Ok(meta) => self.ns_or_default_with_reindex(ns, move |ns| {
ns.policy.update_server_authz(name, meta)
}),
Err(error) => tracing::error!(%error, "Illegal server authorization update"),
}
}
fn delete(&mut self, ns: String, saz: String) {
let _span = info_span!("delete", %ns, %saz).entered();
self.namespaces.get_with_reindex(ns, |ns| {
ns.policy.server_authorizations.remove(&saz).is_some()
fn delete(&mut self, ns: String, name: String) {
let _span = info_span!("delete", %ns, %name).entered();
self.ns_with_reindex(ns, |ns| {
ns.policy.server_authorizations.remove(&name).is_some()
})
}
@ -276,14 +317,9 @@ impl kubert::index::IndexNamespacedResource<k8s::policy::ServerAuthorization> fo
) {
let _span = info_span!("reset");
#[derive(Default)]
struct Ns {
added: Vec<(String, server_authorization::ServerAuthz)>,
removed: HashSet<String>,
}
// Aggregate all of the updates by namespace so that we only reindex
// once per namespace.
type Ns = NsUpdate<server_authorization::ServerAuthz>;
let mut updates_by_ns = HashMap::<String, Ns>::default();
for saz in sazs.into_iter() {
let namespace = saz
@ -297,7 +333,7 @@ impl kubert::index::IndexNamespacedResource<k8s::policy::ServerAuthorization> fo
.added
.push((name, saz)),
Err(error) => {
tracing::error!(ns = %namespace, saz = %name, %error, "Illegal server authorization update")
tracing::error!(ns = %namespace, %name, %error, "Illegal server authorization update")
}
}
}
@ -311,7 +347,7 @@ impl kubert::index::IndexNamespacedResource<k8s::policy::ServerAuthorization> fo
// want to create a default namespace instance, we just want to
// clear out all resources for the namespace (and then drop the
// whole namespace, if necessary).
self.namespaces.get_with_reindex(namespace, |ns| {
self.ns_with_reindex(namespace, |ns| {
ns.policy.server_authorizations.clear();
true
});
@ -319,8 +355,7 @@ impl kubert::index::IndexNamespacedResource<k8s::policy::ServerAuthorization> fo
// Otherwise, we take greater care to reindex only when the
// state actually changed. The vast majority of resets will see
// no actual data change.
self.namespaces
.get_or_default_with_reindex(namespace, |ns| {
self.ns_or_default_with_reindex(namespace, |ns| {
let mut changed = !removed.is_empty();
for name in removed.into_iter() {
ns.policy.server_authorizations.remove(&name);
@ -335,7 +370,246 @@ impl kubert::index::IndexNamespacedResource<k8s::policy::ServerAuthorization> fo
}
}
// === impl NamespaceIndex ===
impl kubert::index::IndexNamespacedResource<k8s::policy::AuthorizationPolicy> for Index {
fn apply(&mut self, policy: k8s::policy::AuthorizationPolicy) {
let ns = policy.namespace().unwrap();
let name = policy.name();
let _span = info_span!("apply", %ns, saz = %name).entered();
let spec = match authorization_policy::Spec::try_from(policy.spec) {
Ok(spec) => spec,
Err(error) => {
tracing::warn!(%error, "Invalid authorization policy");
return;
}
};
self.ns_or_default_with_reindex(ns, |ns| ns.policy.update_authz_policy(name, spec))
}
fn delete(&mut self, ns: String, ap: String) {
let _span = info_span!("delete", %ns, %ap).entered();
self.ns_with_reindex(ns, |ns| {
ns.policy.authorization_policies.remove(&ap).is_some()
})
}
fn reset(
&mut self,
policies: Vec<k8s::policy::AuthorizationPolicy>,
deleted: HashMap<String, HashSet<String>>,
) {
let _span = info_span!("reset");
// Aggregate all of the updates by namespace so that we only reindex
// once per namespace.
type Ns = NsUpdate<authorization_policy::Spec>;
let mut updates_by_ns = HashMap::<String, Ns>::default();
for policy in policies.into_iter() {
let namespace = policy
.namespace()
.expect("authorizationpolicy must be namespaced");
let name = policy.name();
match authorization_policy::Spec::try_from(policy.spec) {
Ok(spec) => updates_by_ns
.entry(namespace)
.or_default()
.added
.push((name, spec)),
Err(error) => {
tracing::error!(ns = %namespace, %name, %error, "Illegal server authorization update")
}
}
}
for (ns, names) in deleted.into_iter() {
updates_by_ns.entry(ns).or_default().removed = names;
}
for (namespace, Ns { added, removed }) in updates_by_ns.into_iter() {
if added.is_empty() {
// If there are no live resources in the namespace, we do not
// want to create a default namespace instance, we just want to
// clear out all resources for the namespace (and then drop the
// whole namespace, if necessary).
self.ns_with_reindex(namespace, |ns| {
ns.policy.authorization_policies.clear();
true
});
} else {
// Otherwise, we take greater care to reindex only when the
// state actually changed. The vast majority of resets will see
// no actual data change.
self.ns_or_default_with_reindex(namespace, |ns| {
let mut changed = !removed.is_empty();
for name in removed.into_iter() {
ns.policy.authorization_policies.remove(&name);
}
for (name, spec) in added.into_iter() {
changed = ns.policy.update_authz_policy(name, spec) || changed;
}
changed
});
}
}
}
}
impl kubert::index::IndexNamespacedResource<k8s::policy::MeshTLSAuthentication> for Index {
fn apply(&mut self, authn: k8s::policy::MeshTLSAuthentication) {
let ns = authn
.namespace()
.expect("MeshTLSAuthentication must have a namespace");
let name = authn.name();
let _span = info_span!("apply", %ns, %name).entered();
let spec = match meshtls_authentication::Spec::try_from_resource(authn, &self.cluster_info)
{
Ok(spec) => spec,
Err(error) => {
tracing::warn!(%error, "Invalid MeshTLSAuthentication");
return;
}
};
if self.authentications.update_meshtls(ns, name, spec) {
self.reindex_all();
}
}
fn delete(&mut self, ns: String, name: String) {
let _span = info_span!("delete", %ns, %name).entered();
if let Entry::Occupied(mut ns) = self.authentications.by_ns.entry(ns) {
tracing::debug!("Deleting MeshTLSAuthentication");
ns.get_mut().network.remove(&name);
if ns.get().is_empty() {
ns.remove();
}
self.reindex_all();
} else {
tracing::warn!("Namespace already deleted!");
}
}
fn reset(
&mut self,
authns: Vec<k8s::policy::MeshTLSAuthentication>,
deleted: HashMap<String, HashSet<String>>,
) {
let _span = info_span!("reset");
let mut changed = false;
for authn in authns.into_iter() {
let namespace = authn
.namespace()
.expect("meshtlsauthentication must be namespaced");
let name = authn.name();
let spec = match meshtls_authentication::Spec::try_from_resource(
authn,
&self.cluster_info,
) {
Ok(spec) => spec,
Err(error) => {
tracing::warn!(ns = %namespace, %name, %error, "Invalid MeshTLSAuthentication");
return;
}
};
changed = self.authentications.update_meshtls(namespace, name, spec) || changed;
}
for (namespace, names) in deleted.into_iter() {
if let Entry::Occupied(mut ns) = self.authentications.by_ns.entry(namespace) {
for name in names.into_iter() {
ns.get_mut().meshtls.remove(&name);
}
if ns.get().is_empty() {
ns.remove();
}
}
}
if changed {
self.reindex_all();
}
}
}
impl kubert::index::IndexNamespacedResource<k8s::policy::NetworkAuthentication> for Index {
fn apply(&mut self, authn: k8s::policy::NetworkAuthentication) {
let ns = authn.namespace().unwrap();
let name = authn.name();
let _span = info_span!("apply", %ns, %name).entered();
let spec = match network_authentication::Spec::try_from(authn.spec) {
Ok(spec) => spec,
Err(error) => {
tracing::warn!(%error, "Invalid NetworkAuthentication");
return;
}
};
if self.authentications.update_network(ns, name, spec) {
self.reindex_all();
}
}
fn delete(&mut self, ns: String, name: String) {
let _span = info_span!("delete", %ns, %name).entered();
if let Entry::Occupied(mut ns) = self.authentications.by_ns.entry(ns) {
tracing::debug!("Deleting MeshTLSAuthentication");
ns.get_mut().network.remove(&name);
if ns.get().is_empty() {
ns.remove();
}
self.reindex_all();
} else {
tracing::warn!("Namespace already deleted!");
}
}
fn reset(
&mut self,
authns: Vec<k8s::policy::NetworkAuthentication>,
deleted: HashMap<String, HashSet<String>>,
) {
let _span = info_span!("reset");
let mut changed = false;
for authn in authns.into_iter() {
let namespace = authn
.namespace()
.expect("meshtlsauthentication must be namespaced");
let name = authn.name();
let spec = match network_authentication::Spec::try_from(authn.spec) {
Ok(spec) => spec,
Err(error) => {
tracing::warn!(ns = %namespace, %name, %error, "Invalid NetworkAuthentication");
return;
}
};
changed = self.authentications.update_network(namespace, name, spec) || changed;
}
for (namespace, names) in deleted.into_iter() {
if let Entry::Occupied(mut ns) = self.authentications.by_ns.entry(namespace) {
for name in names.into_iter() {
ns.get_mut().meshtls.remove(&name);
}
if ns.get().is_empty() {
ns.remove();
}
}
}
if changed {
self.reindex_all();
}
}
}
// === impl NemspaceIndex ===
impl NamespaceIndex {
fn get_or_default(&mut self, ns: String) -> &mut Namespace {
@ -345,16 +619,21 @@ impl NamespaceIndex {
}
/// Gets the given namespace and, if it exists, passes it to the given
/// function. When the function returns `true`, all pods in the namespace are
/// reindexed or, if the namespace is empty, the namespace is removed
/// entirely.
fn get_with_reindex(&mut self, namespace: String, f: impl FnOnce(&mut Namespace) -> bool) {
/// function. If the function returns true, all pods in the namespace are
/// reindexed; or, if the function returns false and the namespace is empty,
/// it is removed from the index.
fn get_with_reindex(
&mut self,
namespace: String,
authns: &AuthenticationNsIndex,
f: impl FnOnce(&mut Namespace) -> bool,
) {
if let Entry::Occupied(mut ns) = self.by_ns.entry(namespace) {
if f(ns.get_mut()) {
if ns.get().is_empty() {
ns.remove();
} else {
ns.get_mut().reindex();
ns.get_mut().reindex(authns);
}
}
}
@ -366,11 +645,12 @@ impl NamespaceIndex {
fn get_or_default_with_reindex(
&mut self,
namespace: String,
authns: &AuthenticationNsIndex,
f: impl FnOnce(&mut Namespace) -> bool,
) {
let ns = self.get_or_default(namespace);
if f(ns) {
ns.reindex();
ns.reindex(authns);
}
}
}
@ -381,13 +661,15 @@ impl Namespace {
fn new(namespace: String, cluster_info: Arc<ClusterInfo>) -> Self {
Namespace {
pods: PodIndex {
namespace,
namespace: namespace.clone(),
by_name: HashMap::default(),
},
policy: PolicyIndex {
namespace,
cluster_info,
servers: HashMap::default(),
server_authorizations: HashMap::default(),
authorization_policies: HashMap::default(),
},
}
}
@ -399,8 +681,8 @@ impl Namespace {
}
#[inline]
fn reindex(&mut self) {
self.pods.reindex(&self.policy);
fn reindex(&mut self, authns: &AuthenticationNsIndex) {
self.pods.reindex(&self.policy, authns);
}
}
@ -418,16 +700,12 @@ impl PodIndex {
meta: pod::Meta,
port_names: HashMap<String, pod::PortSet>,
) -> Result<Option<&mut Pod>> {
let pod = match self.by_name.entry(name) {
Entry::Vacant(entry) => {
tracing::debug!(?meta, ?port_names, "Creating");
let pod = Pod {
let pod = match self.by_name.entry(name.clone()) {
Entry::Vacant(entry) => entry.insert(Pod {
meta,
port_names,
port_servers: pod::PortMap::default(),
};
entry.insert(pod)
}
}),
Entry::Occupied(entry) => {
let pod = entry.into_mut();
@ -435,16 +713,16 @@ impl PodIndex {
// Pod labels and annotations may change at runtime, but the
// port list may not
if pod.port_names != port_names {
bail!("pod port names must not change");
bail!("pod {} port names must not change", name);
}
// If there aren't meaningful changes, then don't bother doing
// any more work.
if pod.meta == meta {
tracing::trace!("No changes");
tracing::debug!(pod = %name, "No changes");
return Ok(None);
}
tracing::debug!(?meta, "Updating");
tracing::debug!(pod = %name, "Updating");
pod.meta = meta;
pod
}
@ -452,11 +730,11 @@ impl PodIndex {
Ok(Some(pod))
}
fn reindex(&mut self, policy: &PolicyIndex) {
fn reindex(&mut self, policy: &PolicyIndex, authns: &AuthenticationNsIndex) {
let _span = info_span!("reindex", ns = %self.namespace).entered();
for (name, pod) in self.by_name.iter_mut() {
let _span = info_span!("pod", pod = %name).entered();
pod.reindex_servers(policy);
pod.reindex_servers(policy, authns);
}
}
}
@ -465,9 +743,7 @@ impl PodIndex {
impl Pod {
/// Determines the policies for ports on this pod.
fn reindex_servers(&mut self, policy: &PolicyIndex) {
tracing::debug!("Indexing servers for pod");
fn reindex_servers(&mut self, policy: &PolicyIndex, authentications: &AuthenticationNsIndex) {
// Keep track of the ports that are already known in the pod so that, after applying server
// matches, we can ensure remaining ports are set to the default policy.
let mut unmatched_ports = self.port_servers.keys().copied().collect::<pod::PortSet>();
@ -497,7 +773,7 @@ impl Pod {
continue;
}
let s = policy.inbound_server(srvname.clone(), server);
let s = policy.inbound_server(srvname.clone(), server, authentications);
self.update_server(port, srvname, s);
matched_ports.insert(port, srvname.clone());
@ -519,6 +795,7 @@ impl Pod {
fn update_server(&mut self, port: u16, name: &str, server: InboundServer) {
match self.port_servers.entry(port) {
Entry::Vacant(entry) => {
tracing::trace!(port = %port, server = %name, "Creating server");
let (tx, rx) = watch::channel(server);
entry.insert(PodPortServer {
name: Some(name.to_string()),
@ -533,6 +810,7 @@ impl Pod {
// Avoid sending redundant updates.
if ps.name.as_deref() == Some(name) && *ps.rx.borrow() == server {
tracing::trace!(port = %port, server = %name, "Skipped redundant server update");
tracing::trace!(?server);
return;
}
@ -542,6 +820,7 @@ impl Pod {
// make the opportunistic choice to assume the cluster is
// configured coherently so we take the update. The admission
// controller should prevent conflicts.
tracing::trace!(port = %port, server = %name, "Updating server");
ps.name = Some(name.to_string());
ps.tx.send(server).expect("a receiver is held by the index");
}
@ -553,9 +832,9 @@ impl Pod {
/// Updates a pod-port to use the given named server.
fn set_default_server(&mut self, port: u16, config: &ClusterInfo) {
let server = Self::default_inbound_server(port, &self.meta.settings, config);
tracing::debug!(%port, server = %config.default_policy, "Setting default server");
match self.port_servers.entry(port) {
Entry::Vacant(entry) => {
tracing::debug!(%port, server = %config.default_policy, "Creating default server");
let (tx, rx) = watch::channel(server);
entry.insert(PodPortServer { name: None, tx, rx });
}
@ -565,9 +844,11 @@ impl Pod {
// Avoid sending redundant updates.
if *ps.rx.borrow() == server {
tracing::trace!(%port, server = %config.default_policy, "Default server already set");
return;
}
tracing::debug!(%port, server = %config.default_policy, "Setting default server");
ps.name = None;
ps.tx.send(server).expect("a receiver is held by the index");
}
@ -642,10 +923,13 @@ impl Pod {
let networks = if cluster_only {
config.networks.iter().copied().map(Into::into).collect()
} else {
vec![Ipv4Net::default().into(), Ipv6Net::default().into()]
vec![
"0.0.0.0/0".parse::<IpNet>().unwrap().into(),
"::/0".parse::<IpNet>().unwrap().into(),
]
};
authorizations.insert(
format!("default:{}", policy),
AuthorizationRef::Default(policy.to_string()),
ClientAuthorization {
authentication,
networks,
@ -653,9 +937,8 @@ impl Pod {
);
};
tracing::trace!(port, ?settings, %policy, ?protocol, ?authorizations, "default server");
InboundServer {
name: format!("default:{}", policy),
reference: ServerRef::Default(policy.to_string()),
protocol,
authorizations,
}
@ -708,10 +991,32 @@ impl PolicyIndex {
true
}
fn inbound_server(&self, name: String, server: &server::Server) -> InboundServer {
let authorizations = self.client_authzs(&name, server);
fn update_authz_policy(&mut self, name: String, spec: authorization_policy::Spec) -> bool {
match self.authorization_policies.entry(name) {
Entry::Vacant(entry) => {
entry.insert(spec);
}
Entry::Occupied(entry) => {
let ap = entry.into_mut();
if *ap == spec {
return false;
}
*ap = spec;
}
}
true
}
fn inbound_server(
&self,
name: String,
server: &server::Server,
authentications: &AuthenticationNsIndex,
) -> InboundServer {
tracing::trace!(%name, ?server, "Creating inbound server");
let authorizations = self.client_authzs(&name, server, authentications);
InboundServer {
name,
reference: ServerRef::Server(name),
authorizations,
protocol: server.protocol.clone(),
}
@ -721,16 +1026,215 @@ impl PolicyIndex {
&self,
server_name: &str,
server: &server::Server,
) -> HashMap<String, ClientAuthorization> {
self.server_authorizations
.iter()
.filter_map(|(name, saz)| {
authentications: &AuthenticationNsIndex,
) -> HashMap<AuthorizationRef, ClientAuthorization> {
let mut authzs = HashMap::default();
for (name, saz) in self.server_authorizations.iter() {
if saz.server_selector.selects(server_name, &server.labels) {
Some((name.to_string(), saz.authz.clone()))
} else {
None
authzs.insert(
AuthorizationRef::ServerAuthorization(name.to_string()),
saz.authz.clone(),
);
}
}
for (name, spec) in self.authorization_policies.iter() {
match spec.target.server() {
Some(target) if target != server_name => {
tracing::trace!(
ns = %self.namespace,
authorizationpolicy = %name,
server = %server_name,
%target,
"AuthorizationPolicy does not target server",
);
continue;
}
None => continue,
Some(_) => {}
}
tracing::trace!(
ns = %self.namespace,
authorizationpolicy = %name,
server = %server_name,
"AuthorizationPolicy targets server",
);
tracing::trace!(authns = ?spec.authentications);
let authz = match self.policy_client_authz(spec, authentications) {
Ok(authz) => authz,
Err(error) => {
tracing::info!(
server = %server_name,
authorizationpolicy = %name,
%error,
"Illegal AuthorizationPolicy; ignoring",
);
continue;
}
};
let reference = AuthorizationRef::AuthorizationPolicy(name.to_string());
authzs.insert(reference, authz);
}
authzs
}
fn policy_client_authz(
&self,
spec: &authorization_policy::Spec,
all_authentications: &AuthenticationNsIndex,
) -> Result<ClientAuthorization> {
use authorization_policy::AuthenticationTarget;
let mut identities = None;
for tgt in spec.authentications.iter() {
if let AuthenticationTarget::MeshTLS {
ref namespace,
ref name,
} = tgt
{
let namespace = namespace.as_deref().unwrap_or(&self.namespace);
tracing::trace!(ns = %namespace, %name, "Finding MeshTLSAuthentication");
if let Some(ns) = all_authentications.by_ns.get(namespace) {
if let Some(authn) = ns.meshtls.get(name) {
tracing::trace!(ns = %namespace, %name, ids = ?authn.matches, "Found MeshTLSAuthentication");
// There can only be a single required MeshTLSAuthentication. This is
// enforced by the admission controller.
if identities.is_some() {
bail!("policy must not include multiple MeshTLSAuthentications");
}
let ids = authn.matches.clone();
identities = Some(ids);
continue;
}
}
bail!(
"could not find MeshTLSAuthentication {} in namespace {}",
name,
namespace
);
}
}
let mut networks = None;
for tgt in spec.authentications.iter() {
if let AuthenticationTarget::Network {
ref namespace,
ref name,
} = tgt
{
let namespace = namespace.as_deref().unwrap_or(&self.namespace);
tracing::trace!(ns = %namespace, %name, "Finding NetworkAuthentication");
if let Some(ns) = all_authentications.by_ns.get(namespace) {
if let Some(authn) = ns.network.get(name).as_ref() {
tracing::trace!(ns = %namespace, %name, nets = ?authn.matches, "Found NetworkAuthentication");
// There can only be a single required NetworkAuthentication. This is
// enforced by the admission controller.
if networks.is_some() {
bail!("policy must not include multiple NetworkAuthentications");
}
let nets = authn.matches.clone();
networks = Some(nets);
continue;
}
}
bail!(
"could not find NetworkAuthentication {} in namespace {}",
name,
namespace
);
}
}
Ok(ClientAuthorization {
// If MTLS identities are configured, use them. Otherwise, do not require
// authentication.
authentication: identities
.map(ClientAuthentication::TlsAuthenticated)
.unwrap_or(ClientAuthentication::Unauthenticated),
// If networks are configured, use them. Otherwise, this applies to all networks.
networks: networks.unwrap_or_else(|| {
vec![
NetworkMatch {
net: Ipv4Net::default().into(),
except: vec![],
},
NetworkMatch {
net: Ipv6Net::default().into(),
except: vec![],
},
]
}),
})
.collect()
}
}
// === impl AuthenticationNsIndex ===
impl AuthenticationNsIndex {
fn update_meshtls(
&mut self,
namespace: String,
name: String,
spec: meshtls_authentication::Spec,
) -> bool {
match self.by_ns.entry(namespace).or_default().meshtls.entry(name) {
Entry::Vacant(entry) => {
entry.insert(spec);
}
Entry::Occupied(mut entry) => {
if *entry.get() == spec {
return false;
}
entry.insert(spec);
}
}
true
}
fn update_network(
&mut self,
namespace: String,
name: String,
spec: network_authentication::Spec,
) -> bool {
match self.by_ns.entry(namespace).or_default().network.entry(name) {
Entry::Vacant(entry) => {
entry.insert(spec);
}
Entry::Occupied(mut entry) => {
if *entry.get() == spec {
return false;
}
entry.insert(spec);
}
}
true
}
}
// === impl AuthenticationIndex ===
impl AuthenticationIndex {
#[inline]
fn is_empty(&self) -> bool {
self.meshtls.is_empty() && self.network.is_empty()
}
}
// === imp NsUpdate ===
impl<T> Default for NsUpdate<T> {
fn default() -> Self {
Self {
added: vec![],
removed: Default::default(),
}
}
}

View File

@ -23,8 +23,11 @@
#![deny(warnings, rust_2018_idioms)]
#![forbid(unsafe_code)]
mod authorization_policy;
mod defaults;
mod index;
mod meshtls_authentication;
mod network_authentication;
mod pod;
mod server;
mod server_authorization;

View File

@ -0,0 +1,46 @@
use crate::ClusterInfo;
use anyhow::Result;
use linkerd_policy_controller_core::IdentityMatch;
use linkerd_policy_controller_k8s_api::{
policy::MeshTLSAuthentication, ResourceExt, ServiceAccount,
};
#[derive(Debug, PartialEq)]
pub(crate) struct Spec {
pub matches: Vec<IdentityMatch>,
}
impl Spec {
pub(crate) fn try_from_resource(
ma: MeshTLSAuthentication,
cluster: &ClusterInfo,
) -> anyhow::Result<Self> {
let namespace = ma
.namespace()
.expect("MeshTLSAuthentication must have a namespace");
let identities = ma.spec.identities.into_iter().flatten().map(|s| {
Ok(s.parse::<IdentityMatch>()
.expect("identity match parsing is infallible"))
});
let identity_refs = ma.spec.identity_refs.into_iter().flatten().map(|tgt| {
if tgt.targets_kind::<ServiceAccount>() {
let ns = tgt.namespace.as_deref().unwrap_or(&namespace);
let id = cluster.service_account_identity(ns, &tgt.name);
Ok(IdentityMatch::Exact(id))
} else {
anyhow::bail!("unsupported target type: {:?}", tgt.canonical_kind())
}
});
let matches = identities
.chain(identity_refs)
.collect::<Result<Vec<_>>>()?;
if matches.is_empty() {
anyhow::bail!("No identities configured");
}
Ok(Spec { matches })
}
}

View File

@ -0,0 +1,28 @@
use linkerd_policy_controller_core::NetworkMatch;
use linkerd_policy_controller_k8s_api::policy::NetworkAuthenticationSpec;
#[derive(Debug, PartialEq)]
pub(crate) struct Spec {
pub matches: Vec<NetworkMatch>,
}
impl TryFrom<NetworkAuthenticationSpec> for Spec {
type Error = anyhow::Error;
fn try_from(spec: NetworkAuthenticationSpec) -> anyhow::Result<Self> {
let matches = spec
.networks
.into_iter()
.map(|n| NetworkMatch {
net: n.cidr.into(),
except: n.except.into_iter().flatten().map(Into::into).collect(),
})
.collect::<Vec<_>>();
if matches.is_empty() {
anyhow::bail!("No networks configured");
}
Ok(Spec { matches })
}
}

View File

@ -1,13 +1,21 @@
mod annotation;
mod authorization_policy;
mod server_authorization;
use crate::{defaults::DefaultPolicy, index::*, server_authorization::ServerSelector, ClusterInfo};
use ahash::AHashMap as HashMap;
use kubert::index::IndexNamespacedResource;
use linkerd_policy_controller_core::{
ClientAuthentication, ClientAuthorization, IdentityMatch, InboundServer, IpNet, Ipv4Net,
Ipv6Net, NetworkMatch, ProxyProtocol,
AuthorizationRef, ClientAuthentication, ClientAuthorization, IdentityMatch, InboundServer,
IpNet, Ipv4Net, Ipv6Net, NetworkMatch, ProxyProtocol, ServerRef,
};
use linkerd_policy_controller_k8s_api::{
self as k8s, api::core::v1::ContainerPort, policy::server::Port, ResourceExt,
self as k8s,
api::core::v1::ContainerPort,
policy::{server::Port, LocalTargetRef, NamespacedTargetRef},
ResourceExt,
};
use maplit::*;
use tokio::time;
#[test]
@ -19,329 +27,14 @@ fn pod_must_exist_for_lookup() {
.expect_err("pod-0.ns-0 must not exist");
}
#[test]
fn links_named_server_port() {
let test = TestConfig::default();
let mut pod = mk_pod(
"ns-0",
"pod-0",
Some((
"container-0",
Some(ContainerPort {
name: Some("admin-http".to_string()),
container_port: 8080,
protocol: Some("TCP".to_string()),
..ContainerPort::default()
}),
)),
);
pod.labels_mut()
.insert("app".to_string(), "app-0".to_string());
test.index.write().apply(pod);
let mut rx = test
.index
.write()
.pod_server_rx("ns-0", "pod-0", 8080)
.expect("pod-0.ns-0 should exist");
assert_eq!(*rx.borrow_and_update(), test.default_server());
test.index.write().apply(mk_server(
"ns-0",
"srv-admin-http",
Port::Name("admin-http".to_string()),
None,
Some(("app", "app-0")),
Some(k8s::policy::server::ProxyProtocol::Http1),
));
assert!(rx.has_changed().unwrap());
assert_eq!(
*rx.borrow_and_update(),
InboundServer {
name: "srv-admin-http".to_string(),
authorizations: Default::default(),
protocol: ProxyProtocol::Http1,
},
);
struct TestConfig {
index: SharedIndex,
detect_timeout: time::Duration,
default_policy: DefaultPolicy,
cluster: ClusterInfo,
_tracing: tracing::subscriber::DefaultGuard,
}
#[test]
fn links_unnamed_server_port() {
let test = TestConfig::default();
let mut pod = mk_pod("ns-0", "pod-0", Some(("container-0", None)));
pod.labels_mut()
.insert("app".to_string(), "app-0".to_string());
test.index.write().apply(pod);
let mut rx = test
.index
.write()
.pod_server_rx("ns-0", "pod-0", 8080)
.expect("pod-0.ns-0 should exist");
assert_eq!(*rx.borrow_and_update(), test.default_server());
test.index.write().apply(mk_server(
"ns-0",
"srv-8080",
Port::Number(8080),
None,
Some(("app", "app-0")),
Some(k8s::policy::server::ProxyProtocol::Http1),
));
assert!(rx.has_changed().unwrap());
assert_eq!(
*rx.borrow_and_update(),
InboundServer {
name: "srv-8080".to_string(),
authorizations: Default::default(),
protocol: ProxyProtocol::Http1,
},
);
}
#[test]
fn links_server_authz_by_name() {
link_server_authz(ServerSelector::Name("srv-8080".to_string()))
}
#[test]
fn links_server_authz_by_label() {
link_server_authz(ServerSelector::Selector(
Some(("app", "app-0")).into_iter().collect(),
));
}
#[inline]
fn link_server_authz(selector: ServerSelector) {
let test = TestConfig::default();
let mut pod = mk_pod("ns-0", "pod-0", Some(("container-0", None)));
pod.labels_mut()
.insert("app".to_string(), "app-0".to_string());
test.index.write().apply(pod);
let mut rx = test
.index
.write()
.pod_server_rx("ns-0", "pod-0", 8080)
.expect("pod-0.ns-0 should exist");
assert_eq!(*rx.borrow_and_update(), test.default_server());
test.index.write().apply(mk_server(
"ns-0",
"srv-8080",
Port::Number(8080),
Some(("app", "app-0")),
Some(("app", "app-0")),
Some(k8s::policy::server::ProxyProtocol::Http1),
));
assert!(rx.has_changed().unwrap());
assert_eq!(
*rx.borrow_and_update(),
InboundServer {
name: "srv-8080".to_string(),
authorizations: Default::default(),
protocol: ProxyProtocol::Http1,
},
);
test.index.write().apply(mk_server_authz(
"ns-0",
"authz-foo",
selector,
k8s::policy::server_authorization::Client {
networks: Some(vec![k8s::policy::server_authorization::Network {
cidr: "10.0.0.0/8".parse().unwrap(),
except: None,
}]),
unauthenticated: false,
mesh_tls: Some(k8s::policy::server_authorization::MeshTls {
identities: Some(vec!["foo.bar".to_string()]),
..Default::default()
}),
},
));
assert!(rx.has_changed().unwrap());
assert_eq!(rx.borrow().name, "srv-8080");
assert_eq!(rx.borrow().protocol, ProxyProtocol::Http1,);
assert!(rx.borrow().authorizations.contains_key("authz-foo"));
}
#[test]
fn server_update_deselects_pod() {
let test = TestConfig::default();
test.index.write().reset(
vec![mk_pod("ns-0", "pod-0", Some(("container-0", None)))],
Default::default(),
);
let mut srv = mk_server(
"ns-0",
"srv-0",
Port::Number(2222),
None,
None,
Some(k8s::policy::server::ProxyProtocol::Http2),
);
test.index
.write()
.reset(vec![srv.clone()], Default::default());
// The default policy applies for all ports.
let mut rx = test
.index
.write()
.pod_server_rx("ns-0", "pod-0", 2222)
.unwrap();
assert_eq!(
*rx.borrow_and_update(),
InboundServer {
name: "srv-0".into(),
protocol: ProxyProtocol::Http2,
authorizations: Default::default(),
}
);
test.index.write().apply({
srv.spec.pod_selector = Some(("label", "value")).into_iter().collect();
srv
});
assert!(rx.has_changed().unwrap());
assert_eq!(*rx.borrow(), test.default_server());
}
/// Tests that pod servers are configured with defaults based on the
/// workload-defined `DefaultPolicy` policy.
///
/// Iterates through each default policy and validates that it produces expected
/// configurations.
#[test]
fn default_policy_annotated() {
for default in &DEFAULTS {
let test = TestConfig::from_default_policy(match *default {
// Invert default to ensure override applies.
DefaultPolicy::Deny => DefaultPolicy::Allow {
authenticated_only: false,
cluster_only: false,
},
_ => DefaultPolicy::Deny,
});
// Initially create the pod without an annotation and check that it gets
// the global default.
let mut pod = mk_pod("ns-0", "pod-0", Some(("container-0", None)));
test.index
.write()
.reset(vec![pod.clone()], Default::default());
let mut rx = test
.index
.write()
.pod_server_rx("ns-0", "pod-0", 2222)
.expect("pod-0.ns-0 should exist");
assert_eq!(
rx.borrow_and_update().name,
format!("default:{}", test.default_policy)
);
// Update the annotation on the pod and check that the watch is updated
// with the new default.
pod.annotations_mut().insert(
"config.linkerd.io/default-inbound-policy".into(),
default.to_string(),
);
test.index.write().apply(pod);
assert!(rx.has_changed().unwrap());
assert_eq!(rx.borrow().name, format!("default:{}", default));
}
}
/// Tests that an invalid workload annotation is ignored in favor of the global
/// default.
#[tokio::test]
async fn default_policy_annotated_invalid() {
let test = TestConfig::default();
let mut p = mk_pod("ns-0", "pod-0", Some(("container-0", None)));
p.annotations_mut().insert(
"config.linkerd.io/default-inbound-policy".into(),
"bogus".into(),
);
test.index.write().reset(vec![p], Default::default());
// Lookup port 2222 -> default config.
let rx = test
.index
.write()
.pod_server_rx("ns-0", "pod-0", 2222)
.expect("pod must exist in lookups");
assert_eq!(*rx.borrow(), test.default_server());
}
#[test]
fn opaque_annotated() {
for default in &DEFAULTS {
let test = TestConfig::from_default_policy(*default);
let mut p = mk_pod("ns-0", "pod-0", Some(("container-0", None)));
p.annotations_mut()
.insert("config.linkerd.io/opaque-ports".into(), "2222".into());
test.index.write().reset(vec![p], Default::default());
let mut server = test.default_server();
server.protocol = ProxyProtocol::Opaque;
let rx = test
.index
.write()
.pod_server_rx("ns-0", "pod-0", 2222)
.expect("pod-0.ns-0 should exist");
assert_eq!(*rx.borrow(), server);
}
}
#[test]
fn authenticated_annotated() {
for default in &DEFAULTS {
let test = TestConfig::from_default_policy(*default);
let mut p = mk_pod("ns-0", "pod-0", Some(("container-0", None)));
p.annotations_mut().insert(
"config.linkerd.io/proxy-require-identity-inbound-ports".into(),
"2222".into(),
);
test.index.write().reset(vec![p], Default::default());
let config = {
let policy = match *default {
DefaultPolicy::Allow { cluster_only, .. } => DefaultPolicy::Allow {
cluster_only,
authenticated_only: true,
},
DefaultPolicy::Deny => DefaultPolicy::Deny,
};
InboundServer {
name: format!("default:{}", policy),
authorizations: mk_default_policy(policy, test.cluster.networks),
protocol: ProxyProtocol::Detect {
timeout: test.detect_timeout,
},
}
};
let rx = test
.index
.write()
.pod_server_rx("ns-0", "pod-0", 2222)
.expect("pod-0.ns-0 should exist");
assert_eq!(*rx.borrow(), config);
}
}
// === Helpers ===
const DEFAULTS: [DefaultPolicy; 5] = [
DefaultPolicy::Deny,
DefaultPolicy::Allow {
@ -363,21 +56,21 @@ const DEFAULTS: [DefaultPolicy; 5] = [
];
fn mk_pod(
ns: impl Into<String>,
name: impl Into<String>,
containers: impl IntoIterator<Item = (impl Into<String>, impl IntoIterator<Item = ContainerPort>)>,
ns: impl ToString,
name: impl ToString,
containers: impl IntoIterator<Item = (impl ToString, impl IntoIterator<Item = ContainerPort>)>,
) -> k8s::Pod {
k8s::Pod {
metadata: k8s::ObjectMeta {
namespace: Some(ns.into()),
name: Some(name.into()),
namespace: Some(ns.to_string()),
name: Some(name.to_string()),
..Default::default()
},
spec: Some(k8s::api::core::v1::PodSpec {
containers: containers
.into_iter()
.map(|(name, ports)| k8s::api::core::v1::Container {
name: name.into(),
name: name.to_string(),
ports: Some(ports.into_iter().collect()),
..Default::default()
})
@ -389,8 +82,8 @@ fn mk_pod(
}
fn mk_server(
ns: impl Into<String>,
name: impl Into<String>,
ns: impl ToString,
name: impl ToString,
port: Port,
srv_labels: impl IntoIterator<Item = (&'static str, &'static str)>,
pod_labels: impl IntoIterator<Item = (&'static str, &'static str)>,
@ -398,8 +91,8 @@ fn mk_server(
) -> k8s::policy::Server {
k8s::policy::Server {
metadata: k8s::ObjectMeta {
namespace: Some(ns.into()),
name: Some(name.into()),
namespace: Some(ns.to_string()),
name: Some(name.to_string()),
labels: Some(
srv_labels
.into_iter()
@ -416,38 +109,10 @@ fn mk_server(
}
}
fn mk_server_authz(
ns: impl Into<String>,
name: impl Into<String>,
selector: ServerSelector,
client: k8s::policy::server_authorization::Client,
) -> k8s::policy::ServerAuthorization {
k8s::policy::ServerAuthorization {
metadata: k8s::ObjectMeta {
namespace: Some(ns.into()),
name: Some(name.into()),
..Default::default()
},
spec: k8s::policy::ServerAuthorizationSpec {
server: match selector {
ServerSelector::Name(n) => k8s::policy::server_authorization::Server {
name: Some(n),
selector: None,
},
ServerSelector::Selector(s) => k8s::policy::server_authorization::Server {
selector: Some(s),
name: None,
},
},
client,
},
}
}
fn mk_default_policy(
da: DefaultPolicy,
cluster_nets: Vec<IpNet>,
) -> HashMap<String, ClientAuthorization> {
) -> HashMap<AuthorizationRef, ClientAuthorization> {
let all_nets = vec![Ipv4Net::default().into(), Ipv6Net::default().into()];
let cluster_nets = cluster_nets.into_iter().map(NetworkMatch::from).collect();
@ -460,7 +125,7 @@ fn mk_default_policy(
authenticated_only: true,
cluster_only: false,
} => Some((
"default:all-authenticated".into(),
AuthorizationRef::Default("all-authenticated".to_string()),
ClientAuthorization {
authentication: authed,
networks: all_nets,
@ -470,7 +135,7 @@ fn mk_default_policy(
authenticated_only: false,
cluster_only: false,
} => Some((
"default:all-unauthenticated".into(),
AuthorizationRef::Default("all-unauthenticated".to_string()),
ClientAuthorization {
authentication: ClientAuthentication::Unauthenticated,
networks: all_nets,
@ -480,7 +145,7 @@ fn mk_default_policy(
authenticated_only: true,
cluster_only: true,
} => Some((
"default:cluster-authenticated".into(),
AuthorizationRef::Default("cluster-authenticated".to_string()),
ClientAuthorization {
authentication: authed,
networks: cluster_nets,
@ -490,7 +155,7 @@ fn mk_default_policy(
authenticated_only: false,
cluster_only: true,
} => Some((
"default:cluster-unauthenticated".into(),
AuthorizationRef::Default("cluster-unauthenticated".to_string()),
ClientAuthorization {
authentication: ClientAuthentication::Unauthenticated,
networks: cluster_nets,
@ -501,14 +166,6 @@ fn mk_default_policy(
.collect()
}
struct TestConfig {
index: SharedIndex,
detect_timeout: time::Duration,
default_policy: DefaultPolicy,
cluster: ClusterInfo,
_tracing: tracing::subscriber::DefaultGuard,
}
impl TestConfig {
fn from_default_policy(default_policy: DefaultPolicy) -> Self {
let _tracing = Self::init_tracing();
@ -533,7 +190,7 @@ impl TestConfig {
fn default_server(&self) -> InboundServer {
InboundServer {
name: format!("default:{}", self.default_policy),
reference: ServerRef::Default(self.default_policy.to_string()),
authorizations: mk_default_policy(self.default_policy, self.cluster.networks.clone()),
protocol: ProxyProtocol::Detect {
timeout: self.detect_timeout,

View File

@ -0,0 +1,132 @@
use super::*;
/// Tests that pod servers are configured with defaults based on the
/// workload-defined `DefaultPolicy` policy.
///
/// Iterates through each default policy and validates that it produces expected
/// configurations.
#[test]
fn default_policy_annotated() {
for default in &DEFAULTS {
let test = TestConfig::from_default_policy(match *default {
// Invert default to ensure override applies.
DefaultPolicy::Deny => DefaultPolicy::Allow {
authenticated_only: false,
cluster_only: false,
},
_ => DefaultPolicy::Deny,
});
// Initially create the pod without an annotation and check that it gets
// the global default.
let mut pod = mk_pod("ns-0", "pod-0", Some(("container-0", None)));
test.index
.write()
.reset(vec![pod.clone()], Default::default());
let mut rx = test
.index
.write()
.pod_server_rx("ns-0", "pod-0", 2222)
.expect("pod-0.ns-0 should exist");
assert_eq!(
rx.borrow_and_update().reference,
ServerRef::Default(test.default_policy.to_string()),
);
// Update the annotation on the pod and check that the watch is updated
// with the new default.
pod.annotations_mut().insert(
"config.linkerd.io/default-inbound-policy".into(),
default.to_string(),
);
test.index.write().apply(pod);
assert!(rx.has_changed().unwrap());
assert_eq!(
rx.borrow().reference,
ServerRef::Default(default.to_string())
);
}
}
/// Tests that an invalid workload annotation is ignored in favor of the global
/// default.
#[tokio::test]
async fn default_policy_annotated_invalid() {
let test = TestConfig::default();
let mut p = mk_pod("ns-0", "pod-0", Some(("container-0", None)));
p.annotations_mut().insert(
"config.linkerd.io/default-inbound-policy".into(),
"bogus".into(),
);
test.index.write().reset(vec![p], Default::default());
// Lookup port 2222 -> default config.
let rx = test
.index
.write()
.pod_server_rx("ns-0", "pod-0", 2222)
.expect("pod must exist in lookups");
assert_eq!(*rx.borrow(), test.default_server());
}
#[test]
fn opaque_annotated() {
for default in &DEFAULTS {
let test = TestConfig::from_default_policy(*default);
let mut p = mk_pod("ns-0", "pod-0", Some(("container-0", None)));
p.annotations_mut()
.insert("config.linkerd.io/opaque-ports".into(), "2222".into());
test.index.write().reset(vec![p], Default::default());
let mut server = test.default_server();
server.protocol = ProxyProtocol::Opaque;
let rx = test
.index
.write()
.pod_server_rx("ns-0", "pod-0", 2222)
.expect("pod-0.ns-0 should exist");
assert_eq!(*rx.borrow(), server);
}
}
#[test]
fn authenticated_annotated() {
for default in &DEFAULTS {
let test = TestConfig::from_default_policy(*default);
let mut p = mk_pod("ns-0", "pod-0", Some(("container-0", None)));
p.annotations_mut().insert(
"config.linkerd.io/proxy-require-identity-inbound-ports".into(),
"2222".into(),
);
test.index.write().reset(vec![p], Default::default());
let config = {
let policy = match *default {
DefaultPolicy::Allow { cluster_only, .. } => DefaultPolicy::Allow {
cluster_only,
authenticated_only: true,
},
DefaultPolicy::Deny => DefaultPolicy::Deny,
};
InboundServer {
reference: ServerRef::Default(policy.to_string()),
authorizations: mk_default_policy(policy, test.cluster.networks),
protocol: ProxyProtocol::Detect {
timeout: test.detect_timeout,
},
}
};
let rx = test
.index
.write()
.pod_server_rx("ns-0", "pod-0", 2222)
.expect("pod-0.ns-0 should exist");
assert_eq!(*rx.borrow(), config);
}
}

View File

@ -0,0 +1,158 @@
use super::*;
#[test]
fn links_authorization_policy_with_mtls_name() {
let test = TestConfig::default();
let mut pod = mk_pod("ns-0", "pod-0", Some(("container-0", None)));
pod.labels_mut()
.insert("app".to_string(), "app-0".to_string());
test.index.write().apply(pod);
let mut rx = test
.index
.write()
.pod_server_rx("ns-0", "pod-0", 8080)
.expect("pod-0.ns-0 should exist");
assert_eq!(*rx.borrow_and_update(), test.default_server());
test.index.write().apply(mk_server(
"ns-0",
"srv-8080",
Port::Number(8080),
None,
Some(("app", "app-0")),
Some(k8s::policy::server::ProxyProtocol::Http1),
));
assert!(rx.has_changed().unwrap());
assert_eq!(
*rx.borrow_and_update(),
InboundServer {
reference: ServerRef::Server("srv-8080".to_string()),
authorizations: Default::default(),
protocol: ProxyProtocol::Http1,
},
);
let authz = ClientAuthorization {
networks: vec!["10.0.0.0/8".parse::<IpNet>().unwrap().into()],
authentication: ClientAuthentication::TlsAuthenticated(vec![IdentityMatch::Exact(
"foo.bar".to_string(),
)]),
};
test.index.write().apply(mk_authorization_policy(
"ns-0",
"authz-foo",
"srv-8080",
vec![
NamespacedTargetRef {
group: Some("policy.linkerd.io".to_string()),
kind: "NetworkAuthentication".to_string(),
name: "net-foo".to_string(),
..Default::default()
},
NamespacedTargetRef {
group: Some("policy.linkerd.io".to_string()),
kind: "MeshTLSAuthentication".to_string(),
namespace: Some("ns-1".to_string()),
name: "mtls-bar".to_string(),
},
],
));
test.index.write().apply(mk_network_authentication(
"ns-0".to_string(),
"net-foo".to_string(),
vec![k8s::policy::network_authentication::Network {
cidr: "10.0.0.0/8".parse().unwrap(),
except: None,
}],
));
test.index.write().apply(mk_meshtls_authentication(
"ns-1",
"mtls-bar",
Some("foo.bar".to_string()),
None,
));
assert!(rx.has_changed().unwrap());
assert_eq!(
*rx.borrow(),
InboundServer {
reference: ServerRef::Server("srv-8080".to_string()),
authorizations: hashmap!(
AuthorizationRef::AuthorizationPolicy("authz-foo".to_string()) => authz
)
.into_iter()
.collect(),
protocol: ProxyProtocol::Http1,
},
);
}
fn mk_authorization_policy(
ns: impl ToString,
name: impl ToString,
server: impl ToString,
authns: impl IntoIterator<Item = NamespacedTargetRef>,
) -> k8s::policy::AuthorizationPolicy {
k8s::policy::AuthorizationPolicy {
metadata: k8s::ObjectMeta {
namespace: Some(ns.to_string()),
name: Some(name.to_string()),
..Default::default()
},
spec: k8s::policy::AuthorizationPolicySpec {
target_ref: LocalTargetRef {
group: Some("policy.linkerd.io".to_string()),
kind: "Server".to_string(),
name: server.to_string(),
},
required_authentication_refs: authns.into_iter().collect(),
},
}
}
fn mk_meshtls_authentication(
ns: impl ToString,
name: impl ToString,
identities: impl IntoIterator<Item = String>,
refs: impl IntoIterator<Item = NamespacedTargetRef>,
) -> k8s::policy::MeshTLSAuthentication {
let identities = identities.into_iter().collect::<Vec<_>>();
let identity_refs = refs.into_iter().collect::<Vec<_>>();
k8s::policy::MeshTLSAuthentication {
metadata: k8s::ObjectMeta {
namespace: Some(ns.to_string()),
name: Some(name.to_string()),
..Default::default()
},
spec: k8s::policy::MeshTLSAuthenticationSpec {
identities: if identities.is_empty() {
None
} else {
Some(identities)
},
identity_refs: if identity_refs.is_empty() {
None
} else {
Some(identity_refs)
},
},
}
}
fn mk_network_authentication(
ns: impl ToString,
name: impl ToString,
networks: impl IntoIterator<Item = k8s::policy::network_authentication::Network>,
) -> k8s::policy::NetworkAuthentication {
k8s::policy::NetworkAuthentication {
metadata: k8s::ObjectMeta {
namespace: Some(ns.to_string()),
name: Some(name.to_string()),
..Default::default()
},
spec: k8s::policy::NetworkAuthenticationSpec {
networks: networks.into_iter().collect(),
},
}
}

View File

@ -0,0 +1,127 @@
use super::*;
#[test]
fn links_named_server_port() {
let test = TestConfig::default();
let mut pod = mk_pod(
"ns-0",
"pod-0",
Some((
"container-0",
Some(ContainerPort {
name: Some("admin-http".to_string()),
container_port: 8080,
protocol: Some("TCP".to_string()),
..ContainerPort::default()
}),
)),
);
pod.labels_mut()
.insert("app".to_string(), "app-0".to_string());
test.index.write().apply(pod);
let mut rx = test
.index
.write()
.pod_server_rx("ns-0", "pod-0", 8080)
.expect("pod-0.ns-0 should exist");
assert_eq!(*rx.borrow_and_update(), test.default_server());
test.index.write().apply(mk_server(
"ns-0",
"srv-admin-http",
Port::Name("admin-http".to_string()),
None,
Some(("app", "app-0")),
Some(k8s::policy::server::ProxyProtocol::Http1),
));
assert!(rx.has_changed().unwrap());
assert_eq!(
*rx.borrow_and_update(),
InboundServer {
reference: ServerRef::Server("srv-admin-http".to_string()),
authorizations: Default::default(),
protocol: ProxyProtocol::Http1,
},
);
}
#[test]
fn links_unnamed_server_port() {
let test = TestConfig::default();
let mut pod = mk_pod("ns-0", "pod-0", Some(("container-0", None)));
pod.labels_mut()
.insert("app".to_string(), "app-0".to_string());
test.index.write().apply(pod);
let mut rx = test
.index
.write()
.pod_server_rx("ns-0", "pod-0", 8080)
.expect("pod-0.ns-0 should exist");
assert_eq!(*rx.borrow_and_update(), test.default_server());
test.index.write().apply(mk_server(
"ns-0",
"srv-8080",
Port::Number(8080),
None,
Some(("app", "app-0")),
Some(k8s::policy::server::ProxyProtocol::Http1),
));
assert!(rx.has_changed().unwrap());
assert_eq!(
*rx.borrow_and_update(),
InboundServer {
reference: ServerRef::Server("srv-8080".to_string()),
authorizations: Default::default(),
protocol: ProxyProtocol::Http1,
},
);
}
#[test]
fn server_update_deselects_pod() {
let test = TestConfig::default();
test.index.write().reset(
vec![mk_pod("ns-0", "pod-0", Some(("container-0", None)))],
Default::default(),
);
let mut srv = mk_server(
"ns-0",
"srv-0",
Port::Number(2222),
None,
None,
Some(k8s::policy::server::ProxyProtocol::Http2),
);
test.index
.write()
.reset(vec![srv.clone()], Default::default());
// The default policy applies for all ports.
let mut rx = test
.index
.write()
.pod_server_rx("ns-0", "pod-0", 2222)
.unwrap();
assert_eq!(
*rx.borrow_and_update(),
InboundServer {
reference: ServerRef::Server("srv-0".to_string()),
protocol: ProxyProtocol::Http2,
authorizations: Default::default(),
}
);
test.index.write().apply({
srv.spec.pod_selector = Some(("label", "value")).into_iter().collect();
srv
});
assert!(rx.has_changed().unwrap());
assert_eq!(*rx.borrow(), test.default_server());
}

View File

@ -0,0 +1,104 @@
use super::*;
#[test]
fn links_server_authz_by_name() {
link_server_authz(ServerSelector::Name("srv-8080".to_string()))
}
#[test]
fn links_server_authz_by_label() {
link_server_authz(ServerSelector::Selector(
Some(("app", "app-0")).into_iter().collect(),
));
}
#[inline]
fn link_server_authz(selector: ServerSelector) {
let test = TestConfig::default();
let mut pod = mk_pod("ns-0", "pod-0", Some(("container-0", None)));
pod.labels_mut()
.insert("app".to_string(), "app-0".to_string());
test.index.write().apply(pod);
let mut rx = test
.index
.write()
.pod_server_rx("ns-0", "pod-0", 8080)
.expect("pod-0.ns-0 should exist");
assert_eq!(*rx.borrow_and_update(), test.default_server());
test.index.write().apply(mk_server(
"ns-0",
"srv-8080",
Port::Number(8080),
Some(("app", "app-0")),
Some(("app", "app-0")),
Some(k8s::policy::server::ProxyProtocol::Http1),
));
assert!(rx.has_changed().unwrap());
assert_eq!(
*rx.borrow_and_update(),
InboundServer {
reference: ServerRef::Server("srv-8080".to_string()),
authorizations: Default::default(),
protocol: ProxyProtocol::Http1,
},
);
test.index.write().apply(mk_server_authz(
"ns-0",
"authz-foo",
selector,
k8s::policy::server_authorization::Client {
networks: Some(vec![k8s::policy::server_authorization::Network {
cidr: "10.0.0.0/8".parse().unwrap(),
except: None,
}]),
unauthenticated: false,
mesh_tls: Some(k8s::policy::server_authorization::MeshTls {
identities: Some(vec!["foo.bar".to_string()]),
..Default::default()
}),
},
));
assert!(rx.has_changed().unwrap());
assert_eq!(
rx.borrow().reference,
ServerRef::Server("srv-8080".to_string())
);
assert_eq!(rx.borrow().protocol, ProxyProtocol::Http1,);
assert!(rx
.borrow()
.authorizations
.contains_key(&AuthorizationRef::ServerAuthorization(
"authz-foo".to_string()
)));
}
fn mk_server_authz(
ns: impl ToString,
name: impl ToString,
selector: ServerSelector,
client: k8s::policy::server_authorization::Client,
) -> k8s::policy::ServerAuthorization {
k8s::policy::ServerAuthorization {
metadata: k8s::ObjectMeta {
namespace: Some(ns.to_string()),
name: Some(name.to_string()),
..Default::default()
},
spec: k8s::policy::ServerAuthorizationSpec {
server: match selector {
ServerSelector::Name(n) => k8s::policy::server_authorization::Server {
name: Some(n),
selector: None,
},
ServerSelector::Selector(s) => k8s::policy::server_authorization::Server {
selector: Some(s),
name: None,
},
},
client,
},
}
}

View File

@ -1,10 +1,15 @@
use crate::k8s::{
labels,
policy::{Server, ServerAuthorization, ServerAuthorizationSpec, ServerSpec},
policy::{
AuthorizationPolicy, AuthorizationPolicySpec, MeshTLSAuthentication,
MeshTLSAuthenticationSpec, NetworkAuthentication, NetworkAuthenticationSpec, Server,
ServerAuthorization, ServerAuthorizationSpec, ServerSpec,
},
};
use anyhow::{anyhow, bail, Result};
use futures::future;
use hyper::{body::Buf, http, Body, Request, Response};
use k8s_openapi::api::core::v1::ServiceAccount;
use kube::{core::DynamicObject, Resource, ResourceExt};
use serde::de::DeserializeOwned;
use std::task;
@ -91,6 +96,18 @@ impl Admission {
}
async fn admit(self, req: AdmissionRequest) -> AdmissionResponse {
if is_kind::<AuthorizationPolicy>(&req) {
return self.admit_spec::<AuthorizationPolicySpec>(req).await;
}
if is_kind::<MeshTLSAuthentication>(&req) {
return self.admit_spec::<MeshTLSAuthenticationSpec>(req).await;
}
if is_kind::<NetworkAuthentication>(&req) {
return self.admit_spec::<NetworkAuthenticationSpec>(req).await;
}
if is_kind::<Server>(&req) {
return self.admit_spec::<ServerSpec>(req).await;
};
@ -170,6 +187,71 @@ fn parse_spec<T: DeserializeOwned>(req: AdmissionRequest) -> Result<(String, Str
Ok((ns, name, spec))
}
#[async_trait::async_trait]
impl Validate<AuthorizationPolicySpec> for Admission {
async fn validate(self, _ns: &str, _name: &str, spec: AuthorizationPolicySpec) -> Result<()> {
// TODO support namespace references?
if !spec.target_ref.targets_kind::<Server>() {
bail!(
"invalid targetRef kind: {}",
spec.target_ref.canonical_kind()
);
}
let mtls_authns_count = spec
.required_authentication_refs
.iter()
.filter(|authn| authn.targets_kind::<MeshTLSAuthentication>())
.count();
if mtls_authns_count > 1 {
bail!("only a single MeshTLSAuthentication may be set");
}
let net_authns_count = spec
.required_authentication_refs
.iter()
.filter(|authn| authn.targets_kind::<NetworkAuthentication>())
.count();
if net_authns_count > 1 {
bail!("only a single NetworkAuthentication may be set");
}
if mtls_authns_count + net_authns_count < spec.required_authentication_refs.len() {
let kinds = spec
.required_authentication_refs
.iter()
.filter(|authn| {
!authn.targets_kind::<MeshTLSAuthentication>()
&& !authn.targets_kind::<NetworkAuthentication>()
})
.map(|authn| authn.canonical_kind())
.collect::<Vec<_>>();
bail!("unsupported authentication kind(s): {}", kinds.join(", "));
}
if mtls_authns_count + net_authns_count == 0 {
bail!("at least one authentication reference must be set");
}
Ok(())
}
}
#[async_trait::async_trait]
impl Validate<MeshTLSAuthenticationSpec> for Admission {
async fn validate(self, _ns: &str, _name: &str, spec: MeshTLSAuthenticationSpec) -> Result<()> {
// The CRD validates identity strings, but does not validate identity references.
for id in spec.identity_refs.iter().flatten() {
if !id.targets_kind::<ServiceAccount>() {
bail!("invalid identity target kind: {}", id.canonical_kind());
}
}
Ok(())
}
}
#[async_trait::async_trait]
impl Validate<ServerSpec> for Admission {
/// Checks that `spec` doesn't select the same pod/ports as other existing Servers
@ -212,6 +294,35 @@ impl Admission {
}
}
#[async_trait::async_trait]
impl Validate<NetworkAuthenticationSpec> for Admission {
async fn validate(self, _ns: &str, _name: &str, spec: NetworkAuthenticationSpec) -> Result<()> {
if spec.networks.is_empty() {
bail!("at least one network must be specified");
}
for net in spec.networks.into_iter() {
for except in net.except.into_iter().flatten() {
if except.contains(&net.cidr) {
bail!(
"cidr '{}' is completely negated by exception '{}'",
net.cidr,
except
);
}
if !net.cidr.contains(&except) {
bail!(
"cidr '{}' does not include exception '{}'",
net.cidr,
except
);
}
}
}
Ok(())
}
}
#[async_trait::async_trait]
impl Validate<ServerAuthorizationSpec> for Admission {
async fn validate(self, _ns: &str, _name: &str, spec: ServerAuthorizationSpec) -> Result<()> {

View File

@ -125,6 +125,27 @@ async fn main() -> Result<()> {
.instrument(info_span!("serverauthorizations")),
);
let authz_policies =
runtime.watch_all::<k8s::policy::AuthorizationPolicy>(ListParams::default());
tokio::spawn(
kubert::index::namespaced(index.clone(), authz_policies)
.instrument(info_span!("authorizationpolicies")),
);
let mtls_authns =
runtime.watch_all::<k8s::policy::MeshTLSAuthentication>(ListParams::default());
tokio::spawn(
kubert::index::namespaced(index.clone(), mtls_authns)
.instrument(info_span!("meshtlsauthentications")),
);
let network_authns =
runtime.watch_all::<k8s::policy::NetworkAuthentication>(ListParams::default());
tokio::spawn(
kubert::index::namespaced(index.clone(), network_authns)
.instrument(info_span!("networkauthentications")),
);
// Run the gRPC server, serving results by looking up against the index handle.
tokio::spawn(grpc(
grpc_addr,

View File

@ -155,7 +155,7 @@ impl Runner {
args: Some(
vec![
"wait",
"--timeout=60s",
"--timeout=120s",
"--for=delete",
"--namespace",
ns,
@ -219,13 +219,22 @@ impl Running {
&self.name,
|obj: Option<&k8s::Pod>| -> bool { obj.and_then(get_exit_code).is_some() },
);
match time::timeout(time::Duration::from_secs(60), finished).await {
match time::timeout(time::Duration::from_secs(120), finished).await {
Ok(Ok(())) => {}
Ok(Err(error)) => panic!("Failed to wait for exit code: {}: {}", self.name, error),
Err(_timeout) => panic!("Timeout waiting for exit code: {}", self.name),
};
let curl_pod = api.get(&self.name).await.expect("pod must exist");
get_exit_code(&curl_pod).expect("curl pod must have an exit code")
let ex = get_exit_code(&curl_pod).expect("curl pod must have an exit code");
if let Err(error) = api
.delete(&self.name, &kube::api::DeleteParams::background())
.await
{
tracing::trace!(%error, name = %self.name, "Failed to delete pod");
}
ex
}
}

View File

@ -14,10 +14,10 @@ macro_rules! assert_is_default_all_unauthenticated {
($config:expr) => {
assert_eq!(
$config.labels,
Some((
"name".to_string(),
"default:all-unauthenticated".to_string()
))
vec![
("kind".to_string(), "default".to_string()),
("name".to_string(), "all-unauthenticated".to_string()),
]
.into_iter()
.collect()
);

View File

@ -0,0 +1,212 @@
use linkerd_policy_controller_k8s_api::{
self as api,
policy::{AuthorizationPolicy, AuthorizationPolicySpec, LocalTargetRef, NamespacedTargetRef},
};
use linkerd_policy_test::admission;
#[tokio::test(flavor = "current_thread")]
async fn accepts_valid() {
admission::accepts(|ns| AuthorizationPolicy {
metadata: api::ObjectMeta {
namespace: Some(ns),
name: Some("test".to_string()),
..Default::default()
},
spec: AuthorizationPolicySpec {
target_ref: LocalTargetRef {
group: Some("policy.linkerd.io".to_string()),
kind: "Server".to_string(),
name: "api".to_string(),
},
required_authentication_refs: vec![
NamespacedTargetRef {
group: Some("policy.linkerd.io".to_string()),
kind: "MeshTLSAuthentication".to_string(),
name: "mtls-clients".to_string(),
..Default::default()
},
NamespacedTargetRef {
group: Some("policy.linkerd.io".to_string()),
kind: "NetworkAuthentication".to_string(),
name: "cluster-nets".to_string(),
namespace: Some("linkerd".to_string()),
},
],
},
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn accepts_valid_with_only_meshtls() {
admission::accepts(|ns| AuthorizationPolicy {
metadata: api::ObjectMeta {
namespace: Some(ns),
name: Some("test".to_string()),
..Default::default()
},
spec: AuthorizationPolicySpec {
target_ref: LocalTargetRef {
group: Some("policy.linkerd.io".to_string()),
kind: "Server".to_string(),
name: "api".to_string(),
},
required_authentication_refs: vec![NamespacedTargetRef {
group: Some("policy.linkerd.io".to_string()),
kind: "MeshTLSAuthentication".to_string(),
name: "mtls-clients".to_string(),
..Default::default()
}],
},
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn accepts_valid_with_only_network() {
admission::accepts(|ns| AuthorizationPolicy {
metadata: api::ObjectMeta {
namespace: Some(ns),
name: Some("test".to_string()),
..Default::default()
},
spec: AuthorizationPolicySpec {
target_ref: LocalTargetRef {
group: Some("policy.linkerd.io".to_string()),
kind: "Server".to_string(),
name: "api".to_string(),
},
required_authentication_refs: vec![NamespacedTargetRef {
group: Some("policy.linkerd.io".to_string()),
kind: "NetworkAuthentication".to_string(),
name: "cluster-nets".to_string(),
namespace: Some("linkerd".to_string()),
}],
},
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn rejects_empty() {
admission::rejects(|ns| AuthorizationPolicy {
metadata: api::ObjectMeta {
namespace: Some(ns),
name: Some("test".to_string()),
..Default::default()
},
spec: AuthorizationPolicySpec::default(),
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn rejects_empty_required_authentications() {
admission::rejects(|ns| AuthorizationPolicy {
metadata: api::ObjectMeta {
namespace: Some(ns),
name: Some("test".to_string()),
..Default::default()
},
spec: AuthorizationPolicySpec {
target_ref: LocalTargetRef {
group: Some("policy.linkerd.io".to_string()),
kind: "Server".to_string(),
name: "deny".to_string(),
},
required_authentication_refs: vec![],
},
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn rejects_target_ref_deployment() {
admission::rejects(|ns| AuthorizationPolicy {
metadata: api::ObjectMeta {
namespace: Some(ns),
name: Some("test".to_string()),
..Default::default()
},
spec: AuthorizationPolicySpec {
target_ref: LocalTargetRef {
group: Some("apps".to_string()),
kind: "Deployment".to_string(),
name: "someapp".to_string(),
},
required_authentication_refs: vec![NamespacedTargetRef {
group: Some("policy.linkerd.io".to_string()),
kind: "NetworkAuthentication".to_string(),
namespace: Some("linkerd".to_string()),
name: "cluster-nets".to_string(),
}],
},
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn rejects_duplicate_mtls_authns() {
admission::rejects(|ns| AuthorizationPolicy {
metadata: api::ObjectMeta {
namespace: Some(ns),
name: Some("test".to_string()),
..Default::default()
},
spec: AuthorizationPolicySpec {
target_ref: LocalTargetRef {
group: Some("policy.linkerd.io".to_string()),
kind: "Server".to_string(),
name: "some-srv".to_string(),
},
required_authentication_refs: vec![
NamespacedTargetRef {
group: Some("policy.linkerd.io".to_string()),
kind: "MeshTLSAuthentication".to_string(),
namespace: Some("some-ns".to_string()),
name: "some-ids".to_string(),
},
NamespacedTargetRef {
group: Some("policy.linkerd.io".to_string()),
kind: "MeshTLSAuthentication".to_string(),
namespace: Some("other-ns".to_string()),
name: "other-ids".to_string(),
},
],
},
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn rejects_duplicate_network_authns() {
admission::rejects(|ns| AuthorizationPolicy {
metadata: api::ObjectMeta {
namespace: Some(ns),
name: Some("test".to_string()),
..Default::default()
},
spec: AuthorizationPolicySpec {
target_ref: LocalTargetRef {
group: Some("policy.linkerd.io".to_string()),
kind: "Server".to_string(),
name: "some-srv".to_string(),
},
required_authentication_refs: vec![
NamespacedTargetRef {
group: Some("policy.linkerd.io".to_string()),
kind: "NetworkAuthentication".to_string(),
namespace: Some("some-ns".to_string()),
name: "some-nets".to_string(),
},
NamespacedTargetRef {
group: Some("policy.linkerd.io".to_string()),
kind: "NetworkAuthentication".to_string(),
namespace: Some("other-ns".to_string()),
name: "other-nets".to_string(),
},
],
},
})
.await;
}

View File

@ -0,0 +1,74 @@
use linkerd_policy_controller_k8s_api::{
self as api,
policy::{MeshTLSAuthentication, MeshTLSAuthenticationSpec, NamespacedTargetRef},
};
use linkerd_policy_test::admission;
#[tokio::test(flavor = "current_thread")]
async fn accepts_valid_ref() {
admission::accepts(|ns| MeshTLSAuthentication {
metadata: api::ObjectMeta {
namespace: Some(ns),
name: Some("test".to_string()),
..Default::default()
},
spec: MeshTLSAuthenticationSpec {
identity_refs: Some(vec![NamespacedTargetRef {
kind: "ServiceAccount".to_string(),
name: "default".to_string(),
..Default::default()
}]),
..Default::default()
},
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn accepts_strings() {
admission::accepts(|ns| MeshTLSAuthentication {
metadata: api::ObjectMeta {
namespace: Some(ns),
name: Some("test".to_string()),
..Default::default()
},
spec: MeshTLSAuthenticationSpec {
identities: Some(vec!["example.id".to_string()]),
..Default::default()
},
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn rejects_empty() {
admission::rejects(|ns| MeshTLSAuthentication {
metadata: api::ObjectMeta {
namespace: Some(ns),
name: Some("test".to_string()),
..Default::default()
},
spec: MeshTLSAuthenticationSpec::default(),
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn rejects_both_refs_and_strings() {
admission::rejects(|ns| MeshTLSAuthentication {
metadata: api::ObjectMeta {
namespace: Some(ns),
name: Some("test".to_string()),
..Default::default()
},
spec: MeshTLSAuthenticationSpec {
identities: Some(vec!["example.id".to_string()]),
identity_refs: Some(vec![NamespacedTargetRef {
kind: "ServiceAccount".to_string(),
name: "default".to_string(),
..Default::default()
}]),
},
})
.await;
}

View File

@ -0,0 +1,142 @@
use linkerd_policy_controller_k8s_api::{
self as api,
policy::network_authentication::{Network, NetworkAuthentication, NetworkAuthenticationSpec},
};
use linkerd_policy_test::admission;
#[tokio::test(flavor = "current_thread")]
async fn accepts_valid() {
admission::accepts(|ns| NetworkAuthentication {
metadata: api::ObjectMeta {
namespace: Some(ns),
name: Some("test".to_string()),
..Default::default()
},
spec: NetworkAuthenticationSpec {
networks: vec![
Network {
cidr: "10.1.0.0/24".parse().unwrap(),
except: None,
},
Network {
cidr: "10.1.1.0/24".parse().unwrap(),
except: Some(vec!["10.1.1.0/28".parse().unwrap()]),
},
],
},
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn accepts_ip_except() {
admission::accepts(|ns| NetworkAuthentication {
metadata: api::ObjectMeta {
namespace: Some(ns),
name: Some("test".to_string()),
..Default::default()
},
spec: NetworkAuthenticationSpec {
networks: vec![Network {
cidr: "10.1.0.0/16".parse().unwrap(),
except: Some(vec!["10.1.1.1".parse().unwrap()]),
}],
},
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn rejects_except_whole_cidr() {
admission::rejects(|ns| NetworkAuthentication {
metadata: api::ObjectMeta {
namespace: Some(ns),
name: Some("test".to_string()),
..Default::default()
},
spec: NetworkAuthenticationSpec {
networks: vec![Network {
cidr: "10.1.1.0/24".parse().unwrap(),
except: Some(vec!["10.1.0.0/16".parse().unwrap()]),
}],
},
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn rejects_except_not_in_cidr() {
admission::rejects(|ns| NetworkAuthentication {
metadata: api::ObjectMeta {
namespace: Some(ns),
name: Some("test".to_string()),
..Default::default()
},
spec: NetworkAuthenticationSpec {
networks: vec![Network {
cidr: "10.1.1.0/24".parse().unwrap(),
except: Some(vec!["10.1.2.0/24".parse().unwrap()]),
}],
},
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn rejects_invalid_cidr() {
// Duplicate the CRD with relaxed validation so we can send an invalid CIDR value.
#[derive(
Clone,
Debug,
Default,
kube::CustomResource,
serde::Deserialize,
serde::Serialize,
schemars::JsonSchema,
)]
#[kube(
group = "policy.linkerd.io",
version = "v1alpha1",
kind = "NetworkAuthentication",
namespaced
)]
#[serde(rename_all = "camelCase")]
pub struct NetworkAuthenticationSpec {
pub networks: Vec<Network>,
}
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct Network {
pub cidr: String,
pub except: Option<Vec<String>>,
}
admission::rejects(|ns| NetworkAuthentication {
metadata: api::ObjectMeta {
namespace: Some(ns),
name: Some("test".to_string()),
..Default::default()
},
spec: NetworkAuthenticationSpec {
networks: vec![Network {
cidr: "10.1.0.0/16".to_string(),
except: Some(vec!["bogus".to_string()]),
}],
},
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn rejects_empty() {
admission::rejects(|ns| NetworkAuthentication {
metadata: api::ObjectMeta {
namespace: Some(ns),
name: Some("test".to_string()),
..Default::default()
},
spec: NetworkAuthenticationSpec { networks: vec![] },
})
.await;
}

View File

@ -1,5 +1,6 @@
use futures::prelude::*;
use kube::ResourceExt;
use linkerd_policy_controller_core::{Ipv4Net, Ipv6Net};
use linkerd_policy_controller_k8s_api as k8s;
use linkerd_policy_test::{
assert_is_default_all_unauthenticated, assert_protocol_detect, create, create_ready_pod, grpc,
@ -55,7 +56,7 @@ async fn server_with_server_authorization() {
assert_eq!(config.authorizations, vec![]);
assert_eq!(
config.labels,
convert_args!(hashmap!("name" => "linkerd-admin")),
convert_args!(hashmap!("kind" => "server", "name" => "linkerd-admin")),
);
// Create a server authorizaation that refers to the `linkerd-admin`
@ -99,6 +100,7 @@ async fn server_with_server_authorization() {
assert_eq!(
config.authorizations.first().unwrap().labels,
convert_args!(hashmap!(
"kind" => "serverauthorization",
"name" => "all-admin",
)),
);
@ -118,7 +120,7 @@ async fn server_with_server_authorization() {
);
assert_eq!(
config.labels,
convert_args!(hashmap!("name" => server.name()))
convert_args!(hashmap!("kind" => "server", "name" => server.name()))
);
// Delete the `Server` and ensure that the update reverts to the
@ -139,6 +141,142 @@ async fn server_with_server_authorization() {
.await;
}
/// Creates a pod, watches its policy, and updates policy resources that impact
/// the watch.
#[tokio::test(flavor = "current_thread")]
async fn server_with_authorization_policy() {
with_temp_ns(|client, ns| async move {
// Create a pod that does nothing. It's injected with a proxy, so we can
// attach policies to its admin server.
let pod = create_ready_pod(&client, mk_pause(&ns, "pause")).await;
tracing::trace!(?pod);
// Port-forward to the control plane and start watching the pod's admin
// server's policy and ensure that the first update uses the default
// policy.
let mut policy_api = grpc::PolicyClient::port_forwarded(&client).await;
let mut rx = policy_api
.watch_port(&ns, &pod.name(), 4191)
.await
.expect("failed to establish watch");
let config = rx
.next()
.await
.expect("watch must not fail")
.expect("watch must return an initial config");
tracing::trace!(?config);
assert_is_default_all_unauthenticated!(config);
assert_protocol_detect!(config);
// Create a server that selects the pod's proxy admin server and ensure
// that the update now uses this server, which has no authorizations
let server = create(&client, mk_admin_server(&ns, "linkerd-admin")).await;
let config = rx
.next()
.await
.expect("watch must not fail")
.expect("watch must return an updated config");
tracing::trace!(?config);
assert_eq!(
config.protocol,
Some(grpc::inbound::ProxyProtocol {
kind: Some(grpc::inbound::proxy_protocol::Kind::Http1(
grpc::inbound::proxy_protocol::Http1::default()
)),
}),
);
assert_eq!(config.authorizations, vec![]);
assert_eq!(
config.labels,
convert_args!(hashmap!("kind" => "server", "name" => server.name()))
);
let all_nets = create(
&client,
k8s::policy::NetworkAuthentication {
metadata: kube::api::ObjectMeta {
namespace: Some(ns.clone()),
name: Some("all-admin".to_string()),
..Default::default()
},
spec: k8s::policy::NetworkAuthenticationSpec {
networks: vec![
k8s::policy::network_authentication::Network {
cidr: Ipv4Net::default().into(),
except: None,
},
k8s::policy::network_authentication::Network {
cidr: Ipv6Net::default().into(),
except: None,
},
],
},
},
)
.await;
let authz_policy = create(
&client,
k8s::policy::AuthorizationPolicy {
metadata: kube::api::ObjectMeta {
namespace: Some(ns.clone()),
name: Some("all-admin".to_string()),
..Default::default()
},
spec: k8s::policy::AuthorizationPolicySpec {
target_ref: k8s::policy::LocalTargetRef::from_resource(&server),
required_authentication_refs: vec![
k8s::policy::NamespacedTargetRef::from_resource(&all_nets),
],
},
},
)
.await;
let config = time::timeout(time::Duration::from_secs(10), rx.next())
.await
.expect("watch must update within 10s")
.expect("watch must not fail")
.expect("watch must return an updated config");
tracing::trace!(?config);
assert_eq!(
config.protocol,
Some(grpc::inbound::ProxyProtocol {
kind: Some(grpc::inbound::proxy_protocol::Kind::Http1(
grpc::inbound::proxy_protocol::Http1::default()
)),
}),
);
assert_eq!(config.authorizations.len(), 1);
assert_eq!(
config.authorizations.first().unwrap().labels,
convert_args!(hashmap!(
"kind" => "authorizationpolicy",
"name" => authz_policy.name(),
))
);
assert_eq!(
*config
.authorizations
.first()
.unwrap()
.authentication
.as_ref()
.unwrap(),
grpc::inbound::Authn {
permit: Some(grpc::inbound::authn::Permit::Unauthenticated(
grpc::inbound::authn::PermitUnauthenticated {}
)),
}
);
assert_eq!(
config.labels,
convert_args!(hashmap!("kind" => "server", "name" => server.name()))
);
})
.await;
}
fn mk_pause(ns: &str, name: &str) -> k8s::Pod {
k8s::Pod {
metadata: k8s::ObjectMeta {

View File

@ -0,0 +1,375 @@
use linkerd_policy_controller_k8s_api::{
self as k8s,
policy::{LocalTargetRef, NamespacedTargetRef},
};
use linkerd_policy_test::{create, create_ready_pod, curl, nginx, with_temp_ns, LinkerdInject};
#[tokio::test(flavor = "current_thread")]
async fn meshtls() {
with_temp_ns(|client, ns| async move {
// First create all of the policies we'll need so that the nginx pod
// starts up with the correct policy (to prevent races).
//
// The policy requires that all connections are authenticated with MeshTLS.
let (srv, all_mtls) = tokio::join!(
create(&client, nginx::server(&ns)),
create(&client, all_authenticated(&ns))
);
create(
&client,
authz_policy(
&ns,
"nginx",
&srv,
Some(NamespacedTargetRef::from_resource(&all_mtls)),
),
)
.await;
// Create the nginx pod and wait for it to be ready.
tokio::join!(
create(&client, nginx::service(&ns)),
create_ready_pod(&client, nginx::pod(&ns))
);
let curl = curl::Runner::init(&client, &ns).await;
let (injected, uninjected) = tokio::join!(
curl.run("curl-injected", "http://nginx", LinkerdInject::Enabled),
curl.run("curl-uninjected", "http://nginx", LinkerdInject::Disabled),
);
let (injected_status, uninjected_status) =
tokio::join!(injected.exit_code(), uninjected.exit_code());
assert_eq!(
injected_status, 0,
"uninjected curl must fail to contact nginx"
);
assert_ne!(uninjected_status, 0, "injected curl must contact nginx");
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn network() {
// In order to test the network policy, we need to create the client pod
// before creating the authorization policy. To avoid races, we do this by
// creating a `curl-lock` configmap that prevents curl from actually being
// executed. Once nginx is running with the correct policy, the configmap is
// deleted to unblock the curl pods.
with_temp_ns(|client, ns| async move {
let curl = curl::Runner::init(&client, &ns).await;
curl.create_lock().await;
// Create a curl pod and wait for it to get an IP.
let blessed = curl
.run("curl-blessed", "http://nginx", LinkerdInject::Disabled)
.await;
let blessed_ip = blessed.ip().await;
tracing::debug!(curl.blessed.ip = %blessed_ip);
// Once we know the IP of the (blocked) pod, create an nginx
// authorization policy that permits connections from this pod.
let (srv, allow_ips) = tokio::join!(
create(&client, nginx::server(&ns)),
create(&client, allow_ips(&ns, Some(blessed_ip)))
);
create(
&client,
authz_policy(
&ns,
"nginx",
&srv,
Some(NamespacedTargetRef::from_resource(&allow_ips)),
),
)
.await;
// Start nginx with the policy.
tokio::join!(
create(&client, nginx::service(&ns)),
create_ready_pod(&client, nginx::pod(&ns))
);
// Once the nginx pod is ready, delete the `curl-lock` configmap to
// unblock curl from running.
curl.delete_lock().await;
// The blessed pod should be able to connect to the nginx pod.
let status = blessed.exit_code().await;
assert_eq!(status, 0, "blessed curl pod must succeed");
// Create another curl pod that is not included in the authorization. It
// should fail to connect to the nginx pod.
let status = curl
.run("curl-cursed", "http://nginx", LinkerdInject::Disabled)
.await
.exit_code()
.await;
assert_ne!(status, 0, "cursed curl pod must fail");
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn both() {
// In order to test the network policy, we need to create the client pod
// before creating the authorization policy. To avoid races, we do this by
// creating a `curl-lock` configmap that prevents curl from actually being
// executed. Once nginx is running with the correct policy, the configmap is
// deleted to unblock the curl pods.
with_temp_ns(|client, ns| async move {
let curl = curl::Runner::init(&client, &ns).await;
curl.create_lock().await;
let (blessed_injected, blessed_uninjected) = tokio::join!(
curl.run(
"curl-blessed-injected",
"http://nginx",
LinkerdInject::Enabled,
),
curl.run(
"curl-blessed-uninjected",
"http://nginx",
LinkerdInject::Disabled,
)
);
let (blessed_injected_ip, blessed_uninjected_ip) =
tokio::join!(blessed_injected.ip(), blessed_uninjected.ip(),);
tracing::debug!(curl.blessed.injected.ip = ?blessed_injected_ip);
tracing::debug!(curl.blessed.uninjected.ip = ?blessed_uninjected_ip);
// Once we know the IP of the (blocked) pod, create an nginx
// authorization policy that permits connections from this pod.
let (srv, allow_ips, all_mtls) = tokio::join!(
create(&client, nginx::server(&ns)),
create(
&client,
allow_ips(&ns, vec![blessed_injected_ip, blessed_uninjected_ip]),
),
create(&client, all_authenticated(&ns))
);
create(
&client,
authz_policy(
&ns,
"nginx",
&srv,
vec![
NamespacedTargetRef::from_resource(&allow_ips),
NamespacedTargetRef::from_resource(&all_mtls),
],
),
)
.await;
// Start nginx with the policy.
tokio::join!(
create(&client, nginx::service(&ns)),
create_ready_pod(&client, nginx::pod(&ns))
);
// Once the nginx pod is ready, delete the `curl-lock` configmap to
// unblock curl from running.
curl.delete_lock().await;
tracing::info!("unblocked curl");
let (blessed_injected_status, blessed_uninjected_status) =
tokio::join!(blessed_injected.exit_code(), blessed_uninjected.exit_code());
// The blessed and injected pod should be able to connect to the nginx pod.
assert_eq!(
blessed_injected_status, 0,
"blessed injected curl pod must succeed"
);
// The blessed and uninjected pod should NOT be able to connect to the nginx pod.
assert_ne!(
blessed_uninjected_status, 0,
"blessed uninjected curl pod must NOT succeed"
);
let (cursed_injected, cursed_uninjected) = tokio::join!(
curl.run(
"curl-cursed-injected",
"http://nginx",
LinkerdInject::Enabled,
),
curl.run(
"curl-cursed-uninjected",
"http://nginx",
LinkerdInject::Disabled,
)
);
let (cursed_injected_status, cursed_uninjected_status) =
tokio::join!(cursed_injected.exit_code(), cursed_uninjected.exit_code(),);
assert_ne!(
cursed_injected_status, 0,
"cursed injected curl pod must fail"
);
assert_ne!(
cursed_uninjected_status, 0,
"cursed uninjected curl pod must fail"
);
})
.await;
}
#[tokio::test(flavor = "current_thread")]
async fn either() {
// In order to test the network policy, we need to create the client pod
// before creating the authorization policy. To avoid races, we do this by
// creating a `curl-lock` configmap that prevents curl from actually being
// executed. Once nginx is running with the correct policy, the configmap is
// deleted to unblock the curl pods.
with_temp_ns(|client, ns| async move {
let curl = curl::Runner::init(&client, &ns).await;
curl.create_lock().await;
let (blessed_injected, blessed_uninjected) = tokio::join!(
curl.run(
"curl-blessed-injected",
"http://nginx",
LinkerdInject::Enabled,
),
curl.run(
"curl-blessed-uninjected",
"http://nginx",
LinkerdInject::Disabled,
)
);
let (blessed_injected_ip, blessed_uninjected_ip) =
tokio::join!(blessed_injected.ip(), blessed_uninjected.ip());
tracing::debug!(curl.blessed.injected.ip = ?blessed_injected_ip);
tracing::debug!(curl.blessed.uninjected.ip = ?blessed_uninjected_ip);
// Once we know the IP of the (blocked) pod, create an nginx
// authorization policy that permits connections from this pod.
let (srv, allow_ips, all_mtls) = tokio::join!(
create(&client, nginx::server(&ns)),
create(&client, allow_ips(&ns, vec![blessed_uninjected_ip])),
create(&client, all_authenticated(&ns))
);
tokio::join!(
create(
&client,
authz_policy(
&ns,
"nginx-from-ip",
&srv,
vec![NamespacedTargetRef::from_resource(&allow_ips)],
),
),
create(
&client,
authz_policy(
&ns,
"nginx-from-id",
&srv,
vec![NamespacedTargetRef::from_resource(&all_mtls)],
),
)
);
// Start nginx with the policy.
tokio::join!(
create(&client, nginx::service(&ns)),
create_ready_pod(&client, nginx::pod(&ns)),
);
// Once the nginx pod is ready, delete the `curl-lock` configmap to
// unblock curl from running.
curl.delete_lock().await;
tracing::info!("unblocking curl");
let (blessed_injected_status, blessed_uninjected_status) =
tokio::join!(blessed_injected.exit_code(), blessed_uninjected.exit_code());
// The blessed and injected pod should be able to connect to the nginx pod.
assert_eq!(
blessed_injected_status, 0,
"blessed injected curl pod must succeed"
);
// The blessed and uninjected pod should NOT be able to connect to the nginx pod.
assert_eq!(
blessed_uninjected_status, 0,
"blessed uninjected curl pod must succeed"
);
let (cursed_injected, cursed_uninjected) = tokio::join!(
curl.run(
"curl-cursed-injected",
"http://nginx",
LinkerdInject::Enabled,
),
curl.run(
"curl-cursed-uninjected",
"http://nginx",
LinkerdInject::Disabled,
),
);
let (cursed_injected_status, cursed_uninjected_status) =
tokio::join!(cursed_injected.exit_code(), cursed_uninjected.exit_code());
assert_eq!(
cursed_injected_status, 0,
"cursed injected curl pod must succeed"
);
assert_ne!(
cursed_uninjected_status, 0,
"cursed uninjected curl pod must fail"
);
})
.await;
}
// === helpers ===
fn authz_policy(
ns: &str,
name: &str,
target: &k8s::policy::Server,
authns: impl IntoIterator<Item = NamespacedTargetRef>,
) -> k8s::policy::AuthorizationPolicy {
k8s::policy::AuthorizationPolicy {
metadata: k8s::ObjectMeta {
namespace: Some(ns.to_string()),
name: Some(name.to_string()),
..Default::default()
},
spec: k8s::policy::AuthorizationPolicySpec {
target_ref: LocalTargetRef::from_resource(target),
required_authentication_refs: authns.into_iter().collect(),
},
}
}
fn all_authenticated(ns: &str) -> k8s::policy::MeshTLSAuthentication {
k8s::policy::MeshTLSAuthentication {
metadata: k8s::ObjectMeta {
namespace: Some(ns.to_string()),
name: Some("all-authenticated".to_string()),
..Default::default()
},
spec: k8s::policy::MeshTLSAuthenticationSpec {
identity_refs: None,
identities: Some(vec!["*".to_string()]),
},
}
}
fn allow_ips(
ns: &str,
ips: impl IntoIterator<Item = std::net::IpAddr>,
) -> k8s::policy::NetworkAuthentication {
k8s::policy::NetworkAuthentication {
metadata: k8s::ObjectMeta {
namespace: Some(ns.to_string()),
name: Some("allow-pod".to_string()),
..Default::default()
},
spec: k8s::policy::NetworkAuthenticationSpec {
networks: ips
.into_iter()
.map(|ip| k8s::policy::Network {
cidr: ip.into(),
except: None,
})
.collect(),
},
}
}