Add OIDC client as a platform managed application

Add an OIDC client application to be deployed as a container
for Kubernetes authentication. This application will be packaged
into an RPM and automatically uploaded on controller-0 unlock.

Story: 2006711
Task: 38166

Change-Id: I25c2b5ee7063d77722002df09afcc7d6725ebe8c
Signed-off-by: Teresa Ho <teresa.ho@windriver.com>
This commit is contained in:
Teresa Ho 2020-01-16 06:34:10 -05:00
parent 883c333075
commit a63597fc03
31 changed files with 1206 additions and 29 deletions

View File

@ -1 +1,3 @@
dex-helm
stx-oidc-auth-helm
stx-oidc-client

View File

@ -0,0 +1 @@
stx-oidc-client

View File

@ -0,0 +1,7 @@
TAR_NAME=helm-charts
SHA=92b6289ae93816717a8453cfe62bad51cbdb8ad0
VERSION=1.0.0
TAR="$TAR_NAME-$SHA.tar.gz"
COPY_LIST="${CGCS_BASE}/downloads/$TAR $PKG_BASE/files/* "
TIS_PATCH_VER=0

View File

@ -0,0 +1,77 @@
# Application tunables (maps to metadata)
%global app_name oidc-auth-apps
%global helm_repo stx-platform
# Install location
%global app_folder /usr/local/share/applications/helm
# Build variables
%global helm_folder /usr/lib/helm
%global sha 92b6289ae93816717a8453cfe62bad51cbdb8ad0
Summary: StarlingX OIDC auth Helm charts
Name: dex-helm
Version: 1.0
Release: %{tis_patch_ver}%{?_tis_dist}
License: Apache-2.0
Group: base
Packager: Wind River <info@windriver.com>
URL: unknown
Source0: helm-charts-%{sha}.tar.gz
Source1: repositories.yaml
Source2: index.yaml
Source3: Makefile
Patch01: 0001-Update-Dex-chart-for-Kubernetes-API-1.16.patch
Patch02: 0002-add-image-pull-secrets.patch
BuildArch: noarch
BuildRequires: helm
%description
StarlingX OIDC auth Helm charts
%prep
#%setup
%setup -n helm-charts
%patch01 -p1
%patch02 -p1
%build
# initialize helm
# helm init --client-only does not work if there is no networking
# The following commands do essentially the same as: helm init
%define helm_home %{getenv:HOME}/.helm
mkdir %{helm_home}
mkdir %{helm_home}/repository
mkdir %{helm_home}/repository/cache
mkdir %{helm_home}/repository/local
mkdir %{helm_home}/plugins
mkdir %{helm_home}/starters
mkdir %{helm_home}/cache
mkdir %{helm_home}/cache/archive
# Stage a repository file that only has a local repo
cp %{SOURCE1} %{helm_home}/repository/repositories.yaml
# Stage a local repo index that can be updated by the build
cp %{SOURCE2} %{helm_home}/repository/local/index.yaml
# Make the charts. These produce a tgz file
cp %{SOURCE3} stable
which make
cd stable
make dex
cd -
%install
install -d -m 755 ${RPM_BUILD_ROOT}%{helm_folder}
install -p -D -m 755 stable/*.tgz ${RPM_BUILD_ROOT}%{helm_folder}
%files
%defattr(-,root,root,-)
%{helm_folder}/*

View File

@ -1,7 +1,3 @@
TAR_NAME=helm-charts
SHA=92b6289ae93816717a8453cfe62bad51cbdb8ad0
VERSION=1.0.0
TAR="$TAR_NAME-$SHA.tar.gz"
SRC_DIR="stx-oidc-auth-helm"
COPY_LIST="${CGCS_BASE}/downloads/helm-charts-92b6289ae93816717a8453cfe62bad51cbdb8ad0.tar.gz $PKG_BASE/files/* "
TIS_PATCH_VER=0

View File

@ -8,8 +8,6 @@
# Build variables
%global helm_folder /usr/lib/helm
%global sha 92b6289ae93816717a8453cfe62bad51cbdb8ad0
Summary: StarlingX OIDC auth Helm charts
Name: stx-oidc-auth-helm
Version: 1.0
@ -19,29 +17,19 @@ Group: base
Packager: Wind River <info@windriver.com>
URL: unknown
#Source0: %{name}-%{version}.tar.gz
Source0: helm-charts-%{sha}.tar.gz
Source1: repositories.yaml
Source2: index.yaml
Source3: metadata.yaml
Source4: manifest.yaml
Source5: Makefile
Patch01: 0001-Update-Dex-chart-for-Kubernetes-API-1.16.patch
Patch02: 0002-add-image-pull-secrets.patch
Source0: %{name}-%{version}.tar.gz
BuildArch: noarch
BuildRequires: helm
BuildRequires: dex-helm
Requires: dex-helm
%description
StarlingX OIDC auth Helm charts
%prep
#%setup
%setup -n helm-charts
%patch01 -p1
%patch02 -p1
%setup
%build
# initialize helm
@ -58,10 +46,10 @@ mkdir %{helm_home}/cache
mkdir %{helm_home}/cache/archive
# Stage a repository file that only has a local repo
cp %{SOURCE1} %{helm_home}/repository/repositories.yaml
cp files/repositories.yaml %{helm_home}/repository/repositories.yaml
# Stage a local repo index that can be updated by the build
cp %{SOURCE2} %{helm_home}/repository/local/index.yaml
cp files/index.yaml %{helm_home}/repository/local/index.yaml
# Host a server for the charts
helm serve --repo-path . &
@ -69,9 +57,8 @@ helm repo rm local
helm repo add local http://localhost:8879/charts
# Make the charts. These produce a tgz file
cp %{SOURCE5} stable
cd stable
make dex
cd helm-charts
make oidc-client
cd -
# Terminate helm server (the last backgrounded task)
@ -83,10 +70,11 @@ kill %1
# Setup staging
mkdir -p %{app_staging}
cp %{SOURCE3} %{app_staging}
cp %{SOURCE4} %{app_staging}
cp files/metadata.yaml %{app_staging}
cp manifests/manifest.yaml %{app_staging}
mkdir -p %{app_staging}/charts
cp stable/*.tgz %{app_staging}/charts
cp helm-charts/*.tgz %{app_staging}/charts
cp %{helm_folder}/dex*.tgz %{app_staging}/charts
cd %{app_staging}
# Populate metadata

View File

@ -0,0 +1,3 @@
apiVersion: v1
entries: {}
generated: 2019-01-07T12:33:46.098166523-06:00

View File

@ -0,0 +1,3 @@
app_name: @APP_NAME@
app_version: @APP_VERSION@
helm_repo: @HELM_REPO@

View File

@ -0,0 +1,12 @@
apiVersion: v1
generated: 2019-01-02T15:19:36.215111369-06:00
repositories:
- caFile: ""
cache: /builddir/.helm/repository/cache/local-index.yaml
certFile: ""
keyFile: ""
name: local
password: ""
url: http://127.0.0.1:8879/charts
username: ""

View File

@ -0,0 +1,43 @@
#
# Copyright 2017 The Openstack-Helm Authors.
#
# Copyright (c) 2019 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# It's necessary to set this because some environments don't link sh -> bash.
SHELL := /bin/bash
TASK := build
EXCLUDES := helm-toolkit doc tests tools logs tmp
CHARTS := helm-toolkit $(filter-out $(EXCLUDES), $(patsubst %/.,%,$(wildcard */.)))
.PHONY: $(EXCLUDES) $(CHARTS)
all: $(CHARTS)
$(CHARTS):
@if [ -d $@ ]; then \
echo; \
echo "===== Processing [$@] chart ====="; \
make $(TASK)-$@; \
fi
init-%:
if [ -f $*/Makefile ]; then make -C $*; fi
if [ -f $*/requirements.yaml ]; then helm dep up $*; fi
lint-%: init-%
if [ -d $* ]; then helm lint $*; fi
build-%: lint-%
if [ -d $* ]; then helm package $*; fi
clean:
@echo "Clean all build artifacts"
rm -f */templates/_partials.tpl */templates/_globals.tpl
rm -f *tgz */charts/*tgz */requirements.lock
rm -rf */charts */tmpcharts
%:
@:

View File

@ -0,0 +1,5 @@
apiVersion: v1
appVersion: "1.0"
description: A Helm chart for Kubernetes
name: oidc-client
version: 0.1.0

View File

@ -0,0 +1,32 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "stx-oidc-client.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "stx-oidc-client.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if contains $name .Release.Name -}}
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "stx-oidc-client.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
{{- end -}}

View File

@ -0,0 +1,19 @@
{{/*
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
*/}}
apiVersion: v1
kind: ConfigMap
metadata:
name: stx-oidc-client
labels:
app: stx-oidc-client
data:
config.yaml: |-
{{- with .Values.config }}
{{ toYaml . | indent 4 }}
{{- end }}

View File

@ -0,0 +1,70 @@
{{/*
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
*/}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: stx-oidc-client
labels:
app: stx-oidc-client
spec:
replicas: {{ .Values.replicas }}
selector:
matchLabels:
app: stx-oidc-client
template:
metadata:
labels:
app: stx-oidc-client
spec:
imagePullSecrets:
- name: default-registry-key
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["./stx-oidc-client"]
args: ["--config", "config.yaml", "--debug"]
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 5555
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
readinessProbe:
httpGet:
path: /
port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- mountPath: "/home"
name: dex-client-secret-volume
- name: config
subPath: config.yaml
mountPath: /app/config.yaml
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
- name: dex-client-secret-volume
secret:
secretName: dex-client-secret
- name: config
configMap:
name: stx-oidc-client

View File

@ -0,0 +1,46 @@
{{/*
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
*/}}
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "stx-oidc-client.fullname" . -}}
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
app.kubernetes.io/name: {{ include "stx-oidc-client.name" . }}
helm.sh/chart: {{ include "stx-oidc-client.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ . }}
backend:
serviceName: {{ $fullName }}
servicePort: http
{{- end }}
{{- end }}
{{- end }}

View File

@ -0,0 +1,23 @@
{{/*
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
*/}}
apiVersion: v1
kind: Service
metadata:
name: stx-oidc-client
labels:
app: stx-oidc-client
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: 5555
protocol: TCP
name: http
nodePort: {{ .Values.service.nodePort }}
selector:
app: stx-oidc-client

View File

@ -0,0 +1,48 @@
#
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# Default values for stx-oidc-client.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicas: 1
image:
repository: docker.io/starlingx/stx-oidc-client
tag: v1.0.0
pullPolicy: IfNotPresent
nameOverride: ""
fullnameOverride: ""
service:
type: NodePort
port: 5555
nodePort: 30555
config:
client_id: stx-oidc-client-app
client_secret: St8rlingX
issuer: https://10.10.10.3:30556/dex
issuer_root_ca: /home/dex-ca.pem
listen: http://0.0.0.0:5555
redirect_uri: http://10.10.10.3:30555/callback
ingress:
enabled: false
annotations: {}
hosts:
- host: chart-example.local
paths: []
tls: []
resources: {}
nodeSelector: {}
tolerations: []
affinity: {}

View File

@ -50,6 +50,43 @@ data:
reference: master
dependencies: []
---
schema: armada/Chart/v1
metadata:
schema: metadata/Document/v1
name: kube-system-oidc-client
data:
chart_name: oidc-client
release: oidc-client
namespace: kube-system
wait:
timeout: 1800
labels:
app: dex
install:
no_hooks: false
upgrade:
no_hooks: false
pre:
delete:
- type: job
labels:
app: oidc-client
values:
config:
issuer_root_ca: /home/dex-ca.pem
listen: http://0.0.0.0:5555
nodeSelector:
node-role.kubernetes.io/master: ""
service:
type: NodePort
port: 5555
source:
type: tar
location: http://172.17.0.1:8080/helm_charts/stx-platform/oidc-client-0.1.0.tgz
subpath: oidc-client
reference: master
dependencies: []
---
schema: armada/ChartGroup/v1
metadata:
schema: metadata/Document/v1
@ -59,6 +96,7 @@ data:
sequenced: true
chart_group:
- kube-system-dex
- kube-system-oidc-client
---
schema: armada/Manifest/v1
metadata:

View File

@ -0,0 +1,2 @@
SRC_DIR="stx-oidc-client"
TIS_PATCH_VER=1

View File

@ -0,0 +1,8 @@
FROM golang:latest
WORKDIR /app
ADD . /app/
RUN go get -d -v ./...
RUN go build -o stx-oidc-client .
EXPOSE 5555
CMD ["./stx-oidc-client"]

261
stx-oidc-client/centos/docker/Gopkg.lock generated Normal file
View File

@ -0,0 +1,261 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:d64c893fc7d2c3d395f421b00d21f0adb8ceffc4d3c90299e732b3985ca16eb4"
name = "github.com/coreos/go-oidc"
packages = ["."]
pruneopts = "UT"
revision = "2be1c5b8a260760503f66dc0996e102b683b3ac3"
version = "v2.1.0"
[[projects]]
digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd"
name = "github.com/fsnotify/fsnotify"
packages = ["."]
pruneopts = "UT"
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
version = "v1.4.7"
[[projects]]
digest = "1:573ca21d3669500ff845bdebee890eb7fc7f0f50c59f2132f2a0c6b03d85086a"
name = "github.com/golang/protobuf"
packages = ["proto"]
pruneopts = "UT"
revision = "6c65a5562fc06764971b7c5d05c76c75e84bdbf7"
version = "v1.3.2"
[[projects]]
digest = "1:c0d19ab64b32ce9fe5cf4ddceba78d5bc9807f0016db6b1183599da3dcc24d10"
name = "github.com/hashicorp/hcl"
packages = [
".",
"hcl/ast",
"hcl/parser",
"hcl/printer",
"hcl/scanner",
"hcl/strconv",
"hcl/token",
"json/parser",
"json/scanner",
"json/token",
]
pruneopts = "UT"
revision = "8cb6e5b959231cc1119e43259c4a608f9c51a241"
version = "v1.0.0"
[[projects]]
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
name = "github.com/inconshreveable/mousetrap"
packages = ["."]
pruneopts = "UT"
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
version = "v1.0"
[[projects]]
digest = "1:5a0ef768465592efca0412f7e838cdc0826712f8447e70e6ccc52eb441e9ab13"
name = "github.com/magiconair/properties"
packages = ["."]
pruneopts = "UT"
revision = "de8848e004dd33dc07a2947b3d76f618a7fc7ef1"
version = "v1.8.1"
[[projects]]
digest = "1:53bc4cd4914cd7cd52139990d5170d6dc99067ae31c56530621b18b35fc30318"
name = "github.com/mitchellh/mapstructure"
packages = ["."]
pruneopts = "UT"
revision = "3536a929edddb9a5b34bd6861dc4a9647cb459fe"
version = "v1.1.2"
[[projects]]
digest = "1:6eea828983c70075ca297bb915ffbcfd3e34c5a50affd94428a65df955c0ff9c"
name = "github.com/pelletier/go-toml"
packages = ["."]
pruneopts = "UT"
revision = "903d9455db9ff1d7ac1ab199062eca7266dd11a3"
version = "v1.6.0"
[[projects]]
branch = "master"
digest = "1:bd9efe4e0b0f768302a1e2f0c22458149278de533e521206e5ddc71848c269a0"
name = "github.com/pquerna/cachecontrol"
packages = [
".",
"cacheobject",
]
pruneopts = "UT"
revision = "1555304b9b35fdd2b425bccf1a5613677705e7d0"
[[projects]]
digest = "1:bb495ec276ab82d3dd08504bbc0594a65de8c3b22c6f2aaa92d05b73fbf3a82e"
name = "github.com/spf13/afero"
packages = [
".",
"mem",
]
pruneopts = "UT"
revision = "588a75ec4f32903aa5e39a2619ba6a4631e28424"
version = "v1.2.2"
[[projects]]
digest = "1:08d65904057412fc0270fc4812a1c90c594186819243160dc779a402d4b6d0bc"
name = "github.com/spf13/cast"
packages = ["."]
pruneopts = "UT"
revision = "8c9545af88b134710ab1cd196795e7f2388358d7"
version = "v1.3.0"
[[projects]]
digest = "1:e096613fb7cf34743d49af87d197663cfccd61876e2219853005a57baedfa562"
name = "github.com/spf13/cobra"
packages = ["."]
pruneopts = "UT"
revision = "f2b07da1e2c38d5f12845a4f607e2e1018cbb1f5"
version = "v0.0.5"
[[projects]]
digest = "1:1b753ec16506f5864d26a28b43703c58831255059644351bbcb019b843950900"
name = "github.com/spf13/jwalterweatherman"
packages = ["."]
pruneopts = "UT"
revision = "94f6ae3ed3bceceafa716478c5fbf8d29ca601a1"
version = "v1.1.0"
[[projects]]
digest = "1:524b71991fc7d9246cc7dc2d9e0886ccb97648091c63e30eef619e6862c955dd"
name = "github.com/spf13/pflag"
packages = ["."]
pruneopts = "UT"
revision = "2e9d26c8c37aae03e3f9d4e90b7116f5accb7cab"
version = "v1.0.5"
[[projects]]
digest = "1:8d0b79a29be9946ea00b2f2b778ec5b36db07453d97abe9d754b25bc2cc49a0e"
name = "github.com/spf13/viper"
packages = ["."]
pruneopts = "UT"
revision = "eabbc68a3ecd5cf8c11a2f84dbda5e7a38493b2f"
version = "v1.6.1"
[[projects]]
digest = "1:f4b32291cad5efac2bfdba89ccde6aa04618b62ce06c1a571da2dc4f3f2677fb"
name = "github.com/subosito/gotenv"
packages = ["."]
pruneopts = "UT"
revision = "2ef7124db659d49edac6aa459693a15ae36c671a"
version = "v1.2.0"
[[projects]]
branch = "master"
digest = "1:5cdf4eb6946922ebe638b90e548e1a86085b778d9daf0889951b39f44e6bbe6c"
name = "golang.org/x/crypto"
packages = [
"ed25519",
"ed25519/internal/edwards25519",
"pbkdf2",
]
pruneopts = "UT"
revision = "4f8c1d86b1ba699e7a66cd649947ed270a74e0bb"
[[projects]]
branch = "master"
digest = "1:f8b491a7c25030a895a0e579742d07136e6958e77ef2d46e769db8eec4e58fcd"
name = "golang.org/x/net"
packages = [
"context",
"context/ctxhttp",
]
pruneopts = "UT"
revision = "fc4aabc6c91483155dade6fbb627cc953a723260"
[[projects]]
branch = "master"
digest = "1:8d1c112fb1679fa097e9a9255a786ee47383fa2549a3da71bcb1334a693ebcfe"
name = "golang.org/x/oauth2"
packages = [
".",
"internal",
]
pruneopts = "UT"
revision = "0f29369cfe4552d0e4bcddc57cc75f4d7e672a33"
[[projects]]
branch = "master"
digest = "1:634414cd43ed62a728aaf9b3e61fde85f012880f9b32b7d00678b6fd887ff9c1"
name = "golang.org/x/sys"
packages = ["unix"]
pruneopts = "UT"
revision = "ac6580df4449443a05718fd7858c1f91ad5f8d20"
[[projects]]
digest = "1:1093f2eb4b344996604f7d8b29a16c5b22ab9e1b25652140d3fede39f640d5cd"
name = "golang.org/x/text"
packages = [
"internal/gen",
"internal/triegen",
"internal/ucd",
"transform",
"unicode/cldr",
"unicode/norm",
]
pruneopts = "UT"
revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475"
version = "v0.3.2"
[[projects]]
digest = "1:e0a1881f9e0564bebdeac98c75e59f07acdde6ed3a5396e0e613eaad31194866"
name = "google.golang.org/appengine"
packages = [
"internal",
"internal/base",
"internal/datastore",
"internal/log",
"internal/remote_api",
"internal/urlfetch",
"urlfetch",
]
pruneopts = "UT"
revision = "971852bfffca25b069c31162ae8f247a3dba083b"
version = "v1.6.5"
[[projects]]
digest = "1:591460fbfc463bda7071ac906bed19cbf64cffd5fcb80a6d1490e87b9dfad942"
name = "gopkg.in/ini.v1"
packages = ["."]
pruneopts = "UT"
revision = "87e589f4917038ae250cff2446db7573f47e97ca"
version = "v1.51.0"
[[projects]]
digest = "1:8ebd7bc4892e673e8d3c6377c71e8c691b92c5e51cd3159efb8343b580213b70"
name = "gopkg.in/square/go-jose.v2"
packages = [
".",
"cipher",
"json",
]
pruneopts = "UT"
revision = "8fd82ff1d52a5162ff23c0a48e2c64a1fa4a3f8f"
version = "v2.4.0"
[[projects]]
digest = "1:b75b3deb2bce8bc079e16bb2aecfe01eb80098f5650f9e93e5643ca8b7b73737"
name = "gopkg.in/yaml.v2"
packages = ["."]
pruneopts = "UT"
revision = "1f64d6156d11335c3f22d9330b0ad14fc1e789ce"
version = "v2.2.7"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/coreos/go-oidc",
"github.com/spf13/cobra",
"github.com/spf13/pflag",
"github.com/spf13/viper",
"golang.org/x/oauth2",
]
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -0,0 +1,42 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/coreos/go-oidc"
version = "2.1.0"
[[constraint]]
name = "github.com/spf13/cobra"
version = "0.0.5"
[[constraint]]
branch = "master"
name = "golang.org/x/oauth2"
[prune]
go-tests = true
unused-packages = true

View File

@ -0,0 +1,371 @@
// Initial file was taken from https://github.com/dexidp/dex/tree/master/cmd/example-app 2019
//
// Copyright (c) 2020 Wind River Systems, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"strings"
"time"
"github.com/coreos/go-oidc"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/oauth2"
)
const exampleAppState = "I wish to wash my irish wristwatch"
var (
config_file string
debug bool
)
type app struct {
clientID string
clientSecret string
redirectURI string
verifier *oidc.IDTokenVerifier
provider *oidc.Provider
// Does the provider use "offline_access" scope to request a refresh token
// or does it use "access_type=offline" (e.g. Google)?
offlineAsScope bool
client *http.Client
}
type Config struct {
a app
issuerURL string
listen string
tlsCert string
tlsKey string
rootCAs string
debug bool
}
// return an HTTP client which trusts the provided root CAs.
func httpClientForRootCAs(rootCAs string) (*http.Client, error) {
tlsConfig := tls.Config{RootCAs: x509.NewCertPool()}
rootCABytes, err := ioutil.ReadFile(rootCAs)
if err != nil {
return nil, fmt.Errorf("failed to read root-ca: %v", err)
}
if !tlsConfig.RootCAs.AppendCertsFromPEM(rootCABytes) {
return nil, fmt.Errorf("no certs found in root CA file %q", rootCAs)
}
return &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tlsConfig,
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
},
}, nil
}
type debugTransport struct {
t http.RoundTripper
}
func (d debugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
reqDump, err := httputil.DumpRequest(req, true)
if err != nil {
return nil, err
}
log.Printf("%s", reqDump)
resp, err := d.t.RoundTrip(req)
if err != nil {
return nil, err
}
respDump, err := httputil.DumpResponse(resp, true)
if err != nil {
resp.Body.Close()
return nil, err
}
log.Printf("%s", respDump)
return resp, nil
}
func start_app(config Config) {
u, err := url.Parse(config.a.redirectURI)
if err != nil {
log.Fatalf("parse redirect-uri: %v", err)
}
listenURL, err := url.Parse(config.listen)
if err != nil {
log.Fatalf("parse listen address: %v", err)
}
if config.rootCAs != "" {
client, err := httpClientForRootCAs(config.rootCAs)
if err != nil {
log.Fatalf("Failed to parse a trusted cert: %v", err)
}
config.a.client = client
}
if debug {
if config.a.client == nil {
config.a.client = &http.Client{
Transport: debugTransport{http.DefaultTransport},
}
} else {
config.a.client.Transport = debugTransport{config.a.client.Transport}
}
}
if config.a.client == nil {
config.a.client = http.DefaultClient
}
ctx := oidc.ClientContext(context.Background(), config.a.client)
provider, err := oidc.NewProvider(ctx, config.issuerURL)
if err != nil {
log.Fatalf("Failed to query provider %q: %v", config.issuerURL, err)
}
var s struct {
ScopesSupported []string `json:"scopes_supported"`
}
if err := provider.Claims(&s); err != nil {
log.Fatalf("Failed to parse provider scopes_supported: %v", err)
}
if len(s.ScopesSupported) == 0 {
// scopes_supported is a "RECOMMENDED" discovery claim, not a required
// one. If missing, assume that the provider follows the spec and has
// an "offline_access" scope.
config.a.offlineAsScope = true
} else {
// See if scopes_supported has the "offline_access" scope.
config.a.offlineAsScope = func() bool {
for _, scope := range s.ScopesSupported {
if scope == oidc.ScopeOfflineAccess {
return true
}
}
return false
}()
}
config.a.provider = provider
config.a.verifier = provider.Verifier(&oidc.Config{ClientID: config.a.clientID})
http.HandleFunc("/", config.a.handleLogin)
http.HandleFunc(u.Path, config.a.handleCallback)
switch listenURL.Scheme {
case "http":
log.Printf("listening on %s", config.listen)
http.ListenAndServe(listenURL.Host, nil)
case "https":
log.Printf("listening on %s", config.listen)
http.ListenAndServeTLS(listenURL.Host, config.tlsCert, config.tlsKey, nil)
default:
fmt.Errorf("listen address %q is not using http or https", config.listen)
}
}
var rootCmd = &cobra.Command{
Use: "oidc-client",
Short: "Dex Kubernetes Client",
Long: "",
Run: func(cmd *cobra.Command, args []string) {
var config Config
err := viper.Unmarshal(&config)
if err != nil {
log.Fatalf("Unable to decode configuration into struct, %v", err)
}
config.issuerURL = viper.GetString("issuer")
config.listen = viper.GetString("listen")
config.rootCAs = viper.GetString("issuer_root_ca")
config.a.clientID = viper.GetString("client_id")
config.a.clientSecret = viper.GetString("client_secret")
config.a.redirectURI = viper.GetString("redirect_uri")
log.Printf("config=%+v", config)
// Start the app
start_app(config)
},
}
// Read in config file
func initConfig() {
if config_file != "" {
viper.SetConfigFile(config_file)
viper.SetConfigType("yaml")
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err != nil {
log.Printf("Fatal error config file: %s \n", err)
} else {
log.Printf("using config file: %s", viper.ConfigFileUsed())
}
}
}
// Initialization
func init() {
cobra.OnInitialize(initConfig)
viper.BindPFlags(rootCmd.Flags())
rootCmd.Flags().StringVar(&config_file, "config", "", "./config.yaml")
rootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Enable debug logging")
}
func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(2)
}
}
func (a *app) oauth2Config(scopes []string) *oauth2.Config {
return &oauth2.Config{
ClientID: a.clientID,
ClientSecret: a.clientSecret,
Endpoint: a.provider.Endpoint(),
Scopes: scopes,
RedirectURL: a.redirectURI,
}
}
func (a *app) handleLogin(w http.ResponseWriter, r *http.Request) {
var scopes []string
if extraScopes := r.FormValue("extra_scopes"); extraScopes != "" {
scopes = strings.Split(extraScopes, " ")
}
var clients []string
if crossClients := r.FormValue("cross_client"); crossClients != "" {
clients = strings.Split(crossClients, " ")
}
for _, client := range clients {
scopes = append(scopes, "audience:server:client_id:"+client)
}
connectorID := ""
if id := r.FormValue("connector_id"); id != "" {
connectorID = id
}
authCodeURL := ""
scopes = append(scopes, "openid", "profile", "email")
if r.FormValue("offline_access") != "yes" {
authCodeURL = a.oauth2Config(scopes).AuthCodeURL(exampleAppState)
} else if a.offlineAsScope {
scopes = append(scopes, "offline_access")
authCodeURL = a.oauth2Config(scopes).AuthCodeURL(exampleAppState)
} else {
authCodeURL = a.oauth2Config(scopes).AuthCodeURL(exampleAppState, oauth2.AccessTypeOffline)
}
if connectorID != "" {
authCodeURL = authCodeURL + "&connector_id=" + connectorID
}
http.Redirect(w, r, authCodeURL, http.StatusSeeOther)
}
func (a *app) handleCallback(w http.ResponseWriter, r *http.Request) {
var (
err error
token *oauth2.Token
)
log.Printf("In handleCallback")
ctx := oidc.ClientContext(r.Context(), a.client)
oauth2Config := a.oauth2Config(nil)
switch r.Method {
case http.MethodGet:
// Authorization redirect callback from OAuth2 auth flow.
if errMsg := r.FormValue("error"); errMsg != "" {
http.Error(w, errMsg+": "+r.FormValue("error_description"), http.StatusBadRequest)
return
}
code := r.FormValue("code")
if code == "" {
http.Error(w, fmt.Sprintf("no code in request: %q", r.Form), http.StatusBadRequest)
return
}
if state := r.FormValue("state"); state != exampleAppState {
http.Error(w, fmt.Sprintf("expected state %q got %q", exampleAppState, state), http.StatusBadRequest)
return
}
token, err = oauth2Config.Exchange(ctx, code)
case http.MethodPost:
// Form request from frontend to refresh a token.
refresh := r.FormValue("refresh_token")
if refresh == "" {
http.Error(w, fmt.Sprintf("no refresh_token in request: %q", r.Form), http.StatusBadRequest)
return
}
t := &oauth2.Token{
RefreshToken: refresh,
Expiry: time.Now().Add(-time.Hour),
}
token, err = oauth2Config.TokenSource(ctx, t).Token()
default:
http.Error(w, fmt.Sprintf("method not implemented: %s", r.Method), http.StatusBadRequest)
return
}
if err != nil {
http.Error(w, fmt.Sprintf("failed to get token: %v", err), http.StatusInternalServerError)
return
}
rawIDToken, ok := token.Extra("id_token").(string)
if !ok {
http.Error(w, "no id_token in token response", http.StatusInternalServerError)
return
}
idToken, err := a.verifier.Verify(r.Context(), rawIDToken)
if err != nil {
http.Error(w, fmt.Sprintf("failed to verify ID token: %v", err), http.StatusInternalServerError)
return
}
accessToken, ok := token.Extra("access_token").(string)
if !ok {
http.Error(w, "no access_token in token response", http.StatusInternalServerError)
return
}
var claims json.RawMessage
if err := idToken.Claims(&claims); err != nil {
http.Error(w, fmt.Sprintf("error decoding ID token claims: %v", err), http.StatusInternalServerError)
return
}
buff := new(bytes.Buffer)
if err := json.Indent(buff, []byte(claims), "", " "); err != nil {
http.Error(w, fmt.Sprintf("error indenting ID token claims: %v", err), http.StatusInternalServerError)
return
}
renderToken(w, a.redirectURI, rawIDToken, accessToken, token.RefreshToken, buff.String())
}

View File

@ -0,0 +1,78 @@
// Initial file was taken from https://github.com/dexidp/dex/tree/master/cmd/example-app 2019
//
// Copyright (c) 2020 Wind River Systems, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
package main
import (
"html/template"
"log"
"net/http"
)
type tokenTmplData struct {
IDToken string
AccessToken string
RefreshToken string
RedirectURL string
Claims string
}
var tokenTmpl = template.Must(template.New("token.html").Parse(`<html>
<head>
<style>
/* make pre wrap */
pre {
white-space: pre-wrap; /* css-3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
</style>
</head>
<body>
<p> ID Token: <pre><code>{{ .IDToken }}</code></pre></p>
<p> Access Token: <pre><code>{{ .AccessToken }}</code></pre></p>
<p> Claims: <pre><code>{{ .Claims }}</code></pre></p>
{{ if .RefreshToken }}
<p> Refresh Token: <pre><code>{{ .RefreshToken }}</code></pre></p>
<form action="{{ .RedirectURL }}" method="post">
<input type="hidden" name="refresh_token" value="{{ .RefreshToken }}">
<input type="submit" value="Redeem refresh token">
</form>
{{ end }}
</body>
</html>
`))
func renderToken(w http.ResponseWriter, redirectURL, idToken, accessToken, refreshToken, claims string) {
renderTemplate(w, tokenTmpl, tokenTmplData{
IDToken: idToken,
AccessToken: accessToken,
RefreshToken: refreshToken,
RedirectURL: redirectURL,
Claims: claims,
})
}
func renderTemplate(w http.ResponseWriter, tmpl *template.Template, data interface{}) {
err := tmpl.Execute(w, data)
if err == nil {
return
}
switch err := err.(type) {
case *template.Error:
// An ExecError guarantees that Execute has not written to the underlying reader.
log.Printf("Error rendering template %s: %s", tmpl.Name(), err)
// TODO(ericchiang): replace with better internal server error.
http.Error(w, "Internal server error", http.StatusInternalServerError)
default:
// An error with the underlying write, such as the connection being
// dropped. Ignore for now.
}
}

View File

@ -0,0 +1,2 @@
BUILDER=docker
LABEL=stx-oidc-client