Skip to content
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
2 changes: 1 addition & 1 deletion .cruft.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"name": "capi-core",
"slug": "capi-core",
"parameter_key": "capi_core",
"test_cases": "defaults old-kustomize-path new-kustomize-path",
"test_cases": "defaults old-kustomize-path new-kustomize-path user-kubeconfig-server",
"add_lib": "y",
"add_pp": "n",
"add_golden": "y",
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ jobs:
- defaults
- old-kustomize-path
- new-kustomize-path
- user-kubeconfig-server
defaults:
run:
working-directory: ${{ env.COMPONENT_NAME }}
Expand All @@ -52,6 +53,7 @@ jobs:
- defaults
- old-kustomize-path
- new-kustomize-path
- user-kubeconfig-server
defaults:
run:
working-directory: ${{ env.COMPONENT_NAME }}
Expand Down
2 changes: 1 addition & 1 deletion Makefile.vars.mk
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@ KUBENT_IMAGE ?= ghcr.io/doitintl/kube-no-trouble:latest
KUBENT_DOCKER ?= $(DOCKER_CMD) $(DOCKER_ARGS) $(root_volume) --entrypoint=/app/kubent $(KUBENT_IMAGE)

instance ?= defaults
test_instances = tests/defaults.yml tests/old-kustomize-path.yml tests/new-kustomize-path.yml
test_instances = tests/defaults.yml tests/old-kustomize-path.yml tests/new-kustomize-path.yml tests/user-kubeconfig-server.yml
1 change: 1 addition & 0 deletions class/capi-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ parameters:
output_path: .
- input_paths:
- ${_base_directory}/component/main.jsonnet
- ${_base_directory}/component/user-kubeconfig.jsonnet
input_type: jsonnet
output_path: capi-core/
- input_paths:
Expand Down
12 changes: 12 additions & 0 deletions class/defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,17 @@ parameters:
registry: registry.k8s.io
image: cluster-api/cluster-api-controller
tag: v1.13.6
caddy:
registry: docker.io
repository: caddy/caddy
tag: 2.11.4-alpine

variables: {}

kubeconfig:
serverURL: ""
apiURL: ""
ingress: {}
oidc:
issuerURL: ""
clientId: ""
116 changes: 116 additions & 0 deletions component/assets/user-kubeconfig-index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<!DOCTYPE html>
<html>
<head>
<title>{{ env "CLUSTER_NAME" }} Kubeconfig</title>
<style>
body {
font-family: 'sans-serif';
width: 50%;
margin: 0 auto;
}

pre {
font-size: 120%;
display: block;
background: #ddd;
border: 1px solid #333;
white-space: pre-wrap;
word-wrap: break-word;
padding: 1ex;
}
code {
user-select: all;
}
ol {
padding: 0 1.5em;
}
li > code {
font-size: 120%;
}

* :has(> [class="copy"]) {
position: relative;
}

[class="copy"]::after {
content: "[copy]";
position: absolute;
right: 6.5em;
top: 0;
margin: 0.5em;
}
li [class="copy"]::after {
right: 0;
}
li {
margin: 0.7ex 0;
}
li > pre {
margin-top: 0.5ex;
margin-bottom: 0.5ex;
}

a.download {
position: absolute;
right: 0;
top: 0;
margin: 0.5em;
color: black;
text-decoration: none;
}

a.download:hover {
font-weight: bold;
}

[class="copy"]:hover::after {
font-weight: bold;
cursor: pointer;
}

.copied > [class="copy"]::after {
content: "[copied]";
}
</style>
<script>
window.onload = () => {
document.querySelectorAll('[class="copy"]').forEach($el =>
$el.addEventListener('click', async ($el) => {
const $node = $el.target;
const content = $node.textContent;
try {
await navigator.clipboard.writeText(content);
} catch (error) {
console.error(error.message);
}
$node.parentNode.classList.add('copied');
setTimeout(($n => () => $n.classList.remove('copied'))($node.parentNode), 1000);
})
);
}
</script>
</head>
<body>
<h1>{{ env "CLUSTER_NAME" }} Kubeconfig</h1>
<p>
<!-- NOTE(sg): the whole <pre> contents must be in a single line to not mess up the formatting! -->
<pre><a class="download" href="./kubeconfig" download="{{ env "CLUSTER_NAME" }}.kubeconfig">[download]</a><code class="copy">{{- readFile "kubeconfig" -}}</code></pre>
</p>
<h1>First time setup</h1>
<ol>
<li>Make sure you have a recent <code>kubectl</code> installed. See the <a href="https://kubernetes.io/docs/tasks/tools/#kubectl">upstream docs</a> for install instructions.</li>
<li>Download the <code>kubectl</code> <a href="https://github.com/int128/kubelogin#setup" target="_blank">kubelogin</a> plugin and ensure it&rsquo;s available in your <code>$PATH</code> as <code>kubectl-oidc_login</code></li>
<li>Verify that the plugin is available.<br/>
<pre class="li"><code class="copy">kubectl oidc-login --version</code></pre>
</li>
<li>Download this cluster&rsquo;s <a href="./kubeconfig" download="{{ env "CLUSTER_NAME" }}.kubeconfig">kubeconfig</a></li>
<li>Set the <code>$KUBECONFIG</code> environment variable<br/>
<pre class="li"><code class="copy">export KUBECONFIG=~/Downloads/{{ env "CLUSTER_NAME" }}.kubeconfig</code></pre>
</li>
<li>Test access<br/>
<pre class="li"><code class="copy">kubectl auth whoami</code></pre>
If everything is setup correctly, this should authenticate you with your VSHN account in your browser and then show some details about your user.
</li>
</ol>
</body>
</html>
103 changes: 103 additions & 0 deletions component/espejote-templates/kubeconfig-manager.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
local esp = import 'espejote.libsonnet';

local config = import 'capi-kubeconfig-manager/config.json';

local kubeconfig =
local kcs = esp.context().kubeconfig;
assert std.length(kcs) == 1 : 'Expected kubeconfig context to have length 1';
assert std.objectHas(kcs[0].data, 'value') : 'Expected kubeconfig secret to have field `value`';
std.parseYaml(std.base64Decode(kcs[0].data.value));

local cluster_ca = std.base64Decode(
kubeconfig.clusters[0].cluster['certificate-authority-data']
);

local user_kubeconfig =
local contextName = 'oidc@%s' % kubeconfig.clusters[0].name;
{
apiVersion: 'v1',
kind: 'Config',
clusters: [
kubeconfig.clusters[0] {
cluster+: {
[if config.apiURL != '' then 'server']:
'https://%s:6443' % config.apiURL,
},
},
],
contexts: [
{
context: {
cluster: kubeconfig.clusters[0].name,
user: 'oidc',
},
name: contextName,
},
],
'current-context': contextName,
users: [
{
name: 'oidc',
user: {
exec: {
apiVersion: 'client.authentication.k8s.io/v1beta1',
args: [
'oidc-login',
'get-token',
'--oidc-issuer-url=%s' % config.oidcIssuerURL,
'--oidc-client-id=%s' % config.oidcClientId,
'--oidc-extra-scope=email offline_access profile openid',
],
command: 'kubectl',
interactiveMode: 'IfAvailable',
provideClusterInfo: false,
},
},
},
],
};
local index_html = importstr 'capi-kubeconfig-manager/index.html';
local caddy_json = importstr 'capi-kubeconfig-manager/caddy.json';
local confighash = std.sha256(
std.manifestJsonMinified(user_kubeconfig) + index_html + caddy_json
);

[
{
apiVersion: 'v1',
kind: 'ConfigMap',
metadata: {
name: config.caddyResourceName,
namespace: config.namespace,
},
data: {
// manifestYamlDoc doesn't add a trailing newline, so we do it
// ourselves.
kubeconfig: std.manifestYamlDoc(user_kubeconfig, quote_keys=false) + '\n',
'cluster-ca.crt': cluster_ca,
'index.html': index_html,
'caddy.json': caddy_json,
},
},
esp.applyOptions(
{
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
name: config.caddyResourceName,
namespace: config.namespace,
},
spec: {
template: {
metadata: {
annotations: {
['%s.syn.tools/config-hash' % config.caddyResourceName]: confighash,
},
},
},
},
},
fieldManagerSuffix=':reloader',
force=true,
),
]
Loading
Loading