Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add APPUiO Cloud Agent deployment #86

Merged
merged 8 commits into from
Apr 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions class/appuio-cloud.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
parameters:
kapitan:
dependencies:
- type: git
output_path: dependencies/appuio-cloud/agent/manifests/${appuio_cloud:images:agent:tag}/
source: https://github.com/appuio/appuio-cloud-agent.git
subdir: config
ref: ${appuio_cloud:images:agent:tag}
compile:
- input_paths:
- appuio-cloud/component/app.jsonnet
Expand All @@ -17,3 +23,7 @@ parameters:
- appuio-cloud/component/runonce-activedeadlineseconds.jsonnet
input_type: jsonnet
output_path: appuio-cloud/
- input_paths:
- appuio-cloud/component/agent.jsonnet
input_type: jsonnet
output_path: appuio-cloud/01_agent/
21 changes: 21 additions & 0 deletions class/defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,27 @@ parameters:
=_metadata: {}
namespace: appuio-cloud

images:
agent:
registry: ghcr.io
repository: appuio/appuio-cloud-agent
tag: v0.1.2

agent:
replicas: 3
resourceRatio:
memoryPerCore: 4Gi
webhook:
tls:
certSecretName: webhook-service-tls
caCertificate: ""
certificate: ""
key: "?{vaultkv:${cluster:tenant}/${cluster:name}/${_instance}/webhook-key}"
namespaceSelector:
matchExpressions:
- key: appuio.io/organization
operator: Exists

bypassNamespaceRestrictions:
roles: {}
clusterRoles:
Expand Down
143 changes: 143 additions & 0 deletions component/agent.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* Deploys the appuio-cloud-agent
*/
local com = import 'lib/commodore.libjsonnet';
local kap = import 'lib/kapitan.libjsonnet';
local kube = import 'lib/kube.libjsonnet';
local inv = kap.inventory();
local params = inv.parameters.appuio_cloud;

local image = params.images.agent;
local loadManifest(manifest) = std.parseJson(kap.yaml_load('appuio-cloud/agent/manifests/' + image.tag + '/' + manifest));

local serviceAccount = loadManifest('rbac/service_account.yaml') {
metadata+: {
namespace: params.namespace,
},
};
local role = com.namespaced(params.namespace, loadManifest('rbac/role.yaml'));
local leaderElectionRole = com.namespaced(params.namespace, loadManifest('rbac/leader_election_role.yaml'));

local webhookCertDir = '/var/run/webhook-service-tls';

local deployment = loadManifest('manager/manager.yaml') {
metadata+: {
namespace: params.namespace,
},
spec+: {
replicas: params.agent.replicas,
template+: {
spec+: {
containers: [
if c.name == 'agent' then
c {
image: '%(registry)s/%(repository)s:%(tag)s' % image,
args: [
'--leader-elect',
'--webhook-cert-dir=' + webhookCertDir,
'--memory-per-core-limit=' + params.agent.resourceRatio.memoryPerCore,
],
volumeMounts+: [
{
name: 'webhook-service-tls',
mountPath: webhookCertDir,
readOnly: true,
},
],
}
else
c
for c in super.containers
],
volumes+: [
{
name: 'webhook-service-tls',
secret: {
secretName: params.agent.webhook.tls.certSecretName,
},
},
],
},
},
},
};

local admissionWebhookTlsSecret =
assert std.length(params.agent.webhook.tls.certificate) > 0 : 'agent.webhook.tls.certificate is required';
assert std.length(params.agent.webhook.tls.key) > 0 : 'agent.webhook.tls.key is required';
kube.Secret(params.agent.webhook.tls.certSecretName) {
metadata+: {
namespace: params.namespace,
},
type: 'kubernetes.io/tls',
stringData: {
'tls.key': params.agent.webhook.tls.key,
'tls.crt': params.agent.webhook.tls.certificate,
},
};

local admissionWebhook = loadManifest('webhook/manifests.yaml') {
metadata+: {
name: '%s-validating-webhook' % params.namespace,
},
webhooks: [
w {
clientConfig+: {
[if std.length(params.agent.webhook.tls.caCertificate) > 0 then 'caBundle']:
std.base64(params.agent.webhook.tls.caCertificate),
service+: {
namespace: params.namespace,
},
},
namespaceSelector: params.agent.webhook.namespaceSelector,
}
for w in super.webhooks
],
};

local admissionWebhookService = loadManifest('webhook/service.yaml') {
metadata+: {
namespace: params.namespace,
},
};

{
'01_role': role,
'01_leader_election_role': leaderElectionRole,
'01_role_binding': kube.ClusterRoleBinding(role.metadata.name) {
roleRef: {
kind: 'ClusterRole',
apiGroup: 'rbac.authorization.k8s.io',
name: role.metadata.name,
},
subjects: [
{
kind: 'ServiceAccount',
name: serviceAccount.metadata.name,
namespace: serviceAccount.metadata.namespace,
},
],
},
'01_leader_election_role_binding': kube.RoleBinding(role.metadata.name) {
metadata+: {
namespace: params.namespace,
},
roleRef: {
kind: 'Role',
apiGroup: 'rbac.authorization.k8s.io',
name: leaderElectionRole.metadata.name,
},
subjects: [
{
kind: 'ServiceAccount',
name: serviceAccount.metadata.name,
namespace: serviceAccount.metadata.namespace,
},
],
},
'01_service_account': serviceAccount,
'02_webhook_cert_secret': admissionWebhookTlsSecret,
'02_deployment': deployment,
'10_webhook_config': admissionWebhook,
'11_webhook_service': admissionWebhookService,
}
72 changes: 72 additions & 0 deletions docs/modules/ROOT/pages/how-tos/tls.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
= Setup TLS certificates for the APPUiO Cloud Agent

This guide provides an example how to setup TLS certificates for the APPUiO Cloud Agent admission webhook server.

====
Requirements

* `kubectl`
* `openssl`
* `vault`
* `yq`
====

. Compile the cluster
+
[source,bash]
----
commodore catalog compile ${CLUSTER_ID}"
----

. Prepare certificate files
+
[source,bash]
----
# Adjust the lifetime as necessary
lifetime=3650

# Adjust admission webhook servicename if the namespace differs
servicename=webhook-service.appuio-cloud.svc
openssl req -x509 -newkey rsa:4096 -nodes -keyout webhook.key -out webhook.crt -days ${lifetime} -subj "/CN=$servicename" -addext "subjectAltName = DNS:$servicename"
----

. Store keys in Vault
+
[source,bash]
----
instance=appuio-cloud
parent="clusters/kv/${TENANT_ID}/${CLUSTER_ID}"

# Use the 'patch' subcommand to add to existing secret
vault kv patch "${parent}/${instance}" webhook-key=@webhook.key
----

. Add certificates to cluster config
+
[source,bash]
----

webhookcert=$(cat webhook.crt)
yq eval -i ".parameters.appuio_cloud.agent.webhook.tls.certificate = \"${webhookcert}\"" \
inventory/classes/${TENANT_ID}/${CLUSTER_ID}.yml
yq eval -i '.parameters.appuio_cloud.agent.webhook.tls.caCertificate = "${appuio_cloud:agent:webhook.tls:certificate}"' \
inventory/classes/${TENANT_ID}/${CLUSTER_ID}.yml
----

. Commit and push configuration change
+
[source,bash]
----
cd inventory/classes/${TENANT_ID}
git add ${CLUSTER_ID}.yml
git commit -m "Configure APPUiO Cloud admission webhook certificates"
git push origin master
popd
----

. Remove temporary files
+
[source,bash]
----
rm webhook.{key,crt}
----
80 changes: 80 additions & 0 deletions docs/modules/ROOT/pages/references/parameters.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,86 @@ default:: `appuio-cloud`

The namespace in which to deploy this component.

== `images`
[horizontal]
type:: dict
glrf marked this conversation as resolved.
Show resolved Hide resolved
default:: https://github.com/appuio/component-appuio-cloud/blob/master/class/defaults.yml[See `class/defaults.yml`]

This parameter allows selecting the Docker images to us.
Each image is specified using keys `registry`, `repository` and `tag`.
This structure allows easily injecting a registry mirror, if required.

== `agent.replicas`

[horizontal]
type:: int
default:: 3

With how many replicas the APPUiO Cloud Agent should run.


== `agent.resourceRatio.memoryPerCore`
type:: string
default:: `4Gi`

The "fair-use" limit of memory to CPU request.
That means, if a namespace requests less than `4Gi` of memory per requested CPU core, the APPUiO Cloud Agent will warn the user that they exceed the "fair-use" limit and will potentially generate additional costs.


== `agent.webhook.tls`

This key configures encryption of traffic to the controller's admission webhook server.
The Kubernetes API server only communicates with admission webhooks over HTTPS.
Therefore, the component requires that both `agent.webhook.tls.certificate` and `agent.webhook.tls.key` are configured.

=== `agent.webhook.tls.certSecretName`

[horizontal]
type:: string
default:: `webhook-service-tls`

The name of the secret containing the TLS certificate and key for the agent's webhook server.

=== `agent.webhook.tls.caCertificate`

[horizontal]
type:: string
default:: `""`

The CA certificate used to sign the webhook service certificate.
If left empty, the component assumes that the provided certificate can be verified using the cluster's default CA bundle.

If you deploy a self-signed certificate, set this parameter to `${appuio_cloud:agent:webhook:tls:certificate}`.

=== `agent.webhook.tls.certificate`

[horizontal]
type:: string
default:: `""`

The certificate to use for the agent's admission webhook server.
Users must provide this parameter, since Kubernetes doesn't support admission webhooks which aren't secured with TLS.

=== `agent.webhook.tls.key`

[horizontal]
type:: string
default:: `?{vaultkv:${cluster:tenant}/${cluster:name}/${_instance}/webhook-key}`

The private key to use for the agent's admission webhook server.

== `agent.webhook.namespaceSelector`
[horizontal]
type:: dict

NamespaceSelector that's applied to every webhook installed by the APPUiO Cloud Agent
The namespaceSelector decides whether to run the webhook on a request for a namespaced resource (or a Namespace object), based on whether the namespace's labels match the selector.
If the object itself is a namespace, the matching is performed on object.metadata.labels.
If the object is a cluster scoped resource other than a Namespace, namespaceSelector has no effect.

See the https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-namespaceselector[upstream documentation] on these selectors.


== `reservedNamespaces`

[horizontal]
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/partials/nav.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
* xref:index.adoc[Home]
* xref:how-tos/tls.adoc[Setup Webook Certificate]
* References
** xref:references/parameters.adoc[Parameters]
include::partial$nav-policy.adoc[]
Expand Down
Loading