diff --git a/.github/workflows/manual_release.yml b/.github/workflows/manual_release.yml new file mode 100644 index 0000000..dc5756a --- /dev/null +++ b/.github/workflows/manual_release.yml @@ -0,0 +1,26 @@ +name: Manually Release Charts + +on: [workflow_dispatch] + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3.1.0 + with: + fetch-depth: 0 + + - name: Configure Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + - name: Install Helm + uses: azure/setup-helm@v3.3 + with: + version: v3.4.0 + + - name: Run chart-releaser + uses: helm/chart-releaser-action@v1.4.1 + env: + CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" \ No newline at end of file diff --git a/.github/workflows/publish_charts.yml b/.github/workflows/publish_charts.yml new file mode 100644 index 0000000..e01136a --- /dev/null +++ b/.github/workflows/publish_charts.yml @@ -0,0 +1,30 @@ +name: Publish charts + +on: + push: + branches: + - master + +jobs: + release: + runs-on: ubuntu-latest + continue-on-error: true + steps: + - name: Checkout + uses: actions/checkout@v3.1.0 + with: + fetch-depth: 0 + + - name: Configure Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + - name: Install Helm + uses: azure/setup-helm@v3.3 + with: + version: v3.4.0 + + - name: Run chart-releaser + uses: helm/chart-releaser-action@v1.4.1 + env: + CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" \ No newline at end of file diff --git a/README.md b/README.md index 620c664..cc98ecc 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,25 @@ go run . The following configuration is required to run the service | Variable | Description | Example | | ------------ | :----------: | ------: | +| crypt4ghKey | Path to public key | `../sda_crypt4gh.pub` | +| expirationDays | Token validity duration in days | 14 | | iss | JWT issuer | `https://issuer.example.com` | -| pathToKey | Path to private key | `../my_key.pem` | +| jwtKey | Path to private key | `../my_key.pub` | +| s3url | The URL to the s3Inbox | `s3.example.com` | | uppmaxUsername | Username for token requester | `some_username` | | uppmaxPassword | Password for token requester | `some_password` | -| s3url | The URL to the s3Inbox | `s3.example.com` | -| expirationDays | Token validity duration in days | 14 | \ No newline at end of file + +## How to deploy +To deploy the service without using vault (e.g. using minikube) in the `lega` namespace, build and push the image using +```sh +docker build -t harbor.nbis.se/uppmax/integration . +docker push harbor.nbis.se/uppmax/integration +``` +Create a secret using +```sh +kubectl -n lega create secret generic --from-file= --from-file= +``` +The names of the files should be added in the values files in `jwt.keyName` and `crypt4ghKey` respectively in the `values.yaml`. Populate the rest of the `values.yaml` file with the correct values and then install using the local copy of the helm charts with +```sh +helm install --namespace lega uppmax charts/uppmax-integration +``` diff --git a/charts/uppmax-integration/Chart.yaml b/charts/uppmax-integration/Chart.yaml index b19d940..1463c97 100644 --- a/charts/uppmax-integration/Chart.yaml +++ b/charts/uppmax-integration/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 0.1.0 +version: 0.2.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/charts/uppmax-integration/templates/_helpers.tpl b/charts/uppmax-integration/templates/_helpers.tpl index 4d6c629..1f52a79 100644 --- a/charts/uppmax-integration/templates/_helpers.tpl +++ b/charts/uppmax-integration/templates/_helpers.tpl @@ -4,3 +4,15 @@ Expand the name of the chart. {{- define "uppmax-integration.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} + +{{- define "TLSissuer" -}} + {{- if and .Values.global.tls.clusterIssuer .Values.global.tls.issuer }} + {{- fail "Only one of global.tls.issuer or global.tls.clusterIssuer should be set" }} + {{- end -}} + + {{- if .Values.global.tls.issuer }} + {{- printf "%s" .Values.global.tls.issuer }} + {{- else if and .Values.global.tls.clusterIssuer }} + {{- printf "%s" .Values.global.tls.clusterIssuer }} + {{- end -}} +{{- end -}} diff --git a/charts/uppmax-integration/templates/certificate.yaml b/charts/uppmax-integration/templates/certificate.yaml new file mode 100644 index 0000000..a4cefa1 --- /dev/null +++ b/charts/uppmax-integration/templates/certificate.yaml @@ -0,0 +1,37 @@ +{{- if or .Values.global.tls.clusterIssuer .Values.global.tls.issuer }} +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ template "uppmax-integration.name" . }}-certs +spec: + # Secret names are always required. + secretName: {{ template "uppmax-integration.name" . }}-certs + + duration: 2160h # 90d + + # The use of the common name field has been deprecated since 2000 and is + # discouraged from being used. + commonName: {{ template "uppmax-integration.name" . }}-certs + isCA: false + privateKey: + algorithm: ECDSA + size: 256 + usages: + - server certs + # At least one of a DNS Name, URI, or IP address is required. + dnsNames: + - {{ template "uppmax-integration.name" . }}-certs + - {{ template "uppmax-integration.name" . }}-certs.{{ .Release.Namespace }}.svc + - {{ template "uppmax-integration.name" . }}-certs.{{ .Release.Namespace }}.svc.cluster.local + ipAddresses: + - 127.0.0.1 + # Issuer references are always required. + issuerRef: + name: {{ template "TLSissuer" . }} + # We can reference ClusterIssuers by changing the kind here. + # The default value is Issuer (i.e. a locally namespaced Issuer) + kind: {{ ternary "Issuer" "ClusterIssuer" (empty .Values.global.tls.clusterIssuer )}} + # This is optional since cert-manager will default to this value however + # if you are using an external issuer, change this to that issuer group. + group: cert-manager.io +{{- end -}} diff --git a/charts/uppmax-integration/templates/deployment.yaml b/charts/uppmax-integration/templates/deployment.yaml index 69ee944..afd2476 100644 --- a/charts/uppmax-integration/templates/deployment.yaml +++ b/charts/uppmax-integration/templates/deployment.yaml @@ -37,19 +37,48 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.imagePullPolicy | quote }} env: + - name: GLOBAL_CRYPT4GHKEY + value: /secrets/{{ .Values.global.crypt4ghKey }} + - name: GLOBAL_EGAUSER + valueFrom: + secretKeyRef: + name: {{ include "uppmax-integration.name" . }}-secret + key: egaUser + - name: GLOBAL_EXPIRATIONDAYS + value: {{ .Values.global.expirationDays | quote }} - name: GLOBAL_ISS value: {{ .Values.global.iss }} - - name: GLOBAL_PATHTOKEY - value: {{ .Values.global.pathToKey }} - - name: GLOBAL_UPPMAXUSERNAME - value: {{ .Values.global.uppmaxUsername }} - - name: GLOBAL_UPPMAXPASSWORD - value: {{ .Values.global.uppmaxPassword }} + - name: GLOBAL_JWTKEY + value: /secrets/{{ .Values.global.jwt.keyName }} - name: GLOBAL_S3URL value: {{ .Values.global.s3url }} - - name: GLOBAL_EXPIRATIONDAYS - value: {{ .Values.global.expirationDays | quote }} - - name: GLOBAL_EGAUSER - value: {{ .Values.global.egaUser }} + - name: GLOBAL_UPPMAXUSERNAME + valueFrom: + secretKeyRef: + name: {{ include "uppmax-integration.name" . }}-secret + key: uppmaxUsername + - name: GLOBAL_UPPMAXPASSWORD + valueFrom: + secretKeyRef: + name: {{ include "uppmax-integration.name" . }}-secret + key: uppmaxPassword securityContext: allowPrivilegeEscalation: false + volumeMounts: + - name: keys + mountPath: /secrets/ + volumes: + - name: keys + projected: + defaultMode: 0440 + sources: + - secret: + name: {{ required "A secret for the JWT signing key is needed" .Values.global.jwt.secretName }} + items: + - key: {{ required "The name of the JWT signing key is needed" .Values.global.jwt.keyName }} + path: {{ .Values.global.jwt.keyName }} + - secret: + name: {{ required "A secret for the crypt4gh public key is needed" .Values.global.jwt.secretName }} + items: + - key: {{ .Values.global.crypt4ghKey }} + path: {{ .Values.global.crypt4ghKey }} diff --git a/charts/uppmax-integration/templates/ingress.yaml b/charts/uppmax-integration/templates/ingress.yaml index 2748da9..ea179f2 100644 --- a/charts/uppmax-integration/templates/ingress.yaml +++ b/charts/uppmax-integration/templates/ingress.yaml @@ -9,6 +9,11 @@ metadata: {{- end }} {{- with .Values.ingress.annotations }} {{- toYaml . | nindent 4 }} + {{- if .Values.ingress.clusterIssuer }} + cert-manager.io/cluster-issuer: {{ .Values.ingress.clusterIssuer | quote }} + {{- else if .Values.ingress.issuer }} + cert-manager.io/issuer: {{ .Values.ingress.issuer | quote }} + {{- end }} {{- end }} spec: {{- if .Values.ingress.ingressClassName }} diff --git a/charts/uppmax-integration/templates/secrets.yaml b/charts/uppmax-integration/templates/secrets.yaml index af21df4..3cdd476 100644 --- a/charts/uppmax-integration/templates/secrets.yaml +++ b/charts/uppmax-integration/templates/secrets.yaml @@ -4,11 +4,8 @@ metadata: name: {{ include "uppmax-integration.name" . }}-secret type: Opaque stringData: - iss: {{ .Values.global.iss | quote }} - pathToKey: {{ .Values.global.pathToKey | quote }} uppmaxUsername: {{ .Values.global.uppmaxUsername | quote }} uppmaxPassword: {{ .Values.global.uppmaxPassword | quote }} - s3url: {{ .Values.global.s3url | quote }} - expirationDays: {{ default 14 .Values.global.expirationDays | quote }} egaUser: {{ .Values.global.egaUser | quote }} + crypt4ghKey: {{ .Values.global.crypt4ghKey }} \ No newline at end of file diff --git a/charts/uppmax-integration/values.yaml b/charts/uppmax-integration/values.yaml index e7f1d93..a04162d 100644 --- a/charts/uppmax-integration/values.yaml +++ b/charts/uppmax-integration/values.yaml @@ -3,35 +3,22 @@ # Declare variables to be passed into your templates. global: - iss: "https://login.test.ega.nbis.se" - pathToKey: "/vault/secrets/jwt.key" - uppmaxUsername: "uppmax" - uppmaxPassword: "uppmax" - s3url: "inbox.test.ega.nbis.se" + iss: "" + jwt: + keyName: "" + secretName: "" + uppmaxUsername: "" + uppmaxPassword: "" + s3url: "" expirationDays: "25" - egaUser: "ega-box-uppmax" + egaUser: "" + crypt4ghKey: "" tls: enabled: false + issuer: "" + clusterIssuer: "" -podAnnotations: - "vault.hashicorp.com/agent-inject": "true" - "vault.hashicorp.com/role": "lega-auth" - "vault.hashicorp.com/agent-requests-cpu": "32m" - "vault.hashicorp.com/ca-cert": "/vault/tls/ca.crt" - "vault.hashicorp.com/tls-secret": "vault-ca" - "vault.hashicorp.com/agent-pre-populate": "false" - "vault.hashicorp.com/agent-run-as-user": "65534" - "vault.hashicorp.com/agent-inject-secret-ca.crt": "service-pki/cert/ca_chain" - "vault.hashicorp.com/agent-inject-template-ca.crt": | - {{- with secret "service-pki/cert/ca_chain" -}} - {{ .Data.certificate }} - {{- end -}} - "vault.hashicorp.com/agent-inject-secret-jwt.key": "transit/export/signing-key/jwt/1" - "vault.hashicorp.com/agent-inject-template-jwt.key": | - {{- with secret "transit/export/signing-key/jwt/1" -}} - {{ index .Data.keys "1" }} - {{- end -}} - +podAnnotations: {} replicaCount: 1 @@ -52,7 +39,7 @@ serviceAccount: annotations: {} # The name of the service account to use. # If not set and create is true, a name is generated using the fullname template - name: "federated-sda" + name: "" service: type: ClusterIP @@ -71,8 +58,8 @@ resources: memory: 128Mi ingress: - hostName: "uppmax.test.ega.nbis.se" - protocol: "https" + hostName: "" + protocol: "" ingressClassName: "nginx" - annotations: - cert-manager.io/cluster-issuer: "letsencrypt-prod" + clusterIssuer: "" + issuer: "" diff --git a/config.yaml b/config.yaml index 0d59194..03d46e9 100644 --- a/config.yaml +++ b/config.yaml @@ -1,9 +1,9 @@ global: - crypt4ghKeyPath: "" + crypt4ghKey: "" egaUser: "" expirationDays: 14 iss: "" - pathToKey: "" + jwtKey: "" s3url: "" uppmaxUsername: "" uppmaxPassword: "" diff --git a/helpers/helpers.go b/helpers/helpers.go index 3e3d6e2..27e1d2d 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -26,16 +26,16 @@ var Config Conf // Conf describes the configuration of the service type Conf struct { - PathToKey string - Iss string + Crypt4ghKeyPath string + Crypt4ghKey string + EgaUser string ExpirationDays int + Iss string + JwtKeyPath string + JwtParsedKey *ecdsa.PrivateKey + S3URL string Username string Password string - S3URL string - EgaUser string - ParsedKey *ecdsa.PrivateKey - Crypt4ghKeyPath string - Crypt4ghKey string } // NewConf reads the configuration from the config.yaml file @@ -64,7 +64,7 @@ func NewConf(conf *Conf) (err error) { } requiredConfVars := []string{ - "global.iss", "global.pathToKey", "global.uppmaxUsername", "global.uppmaxPassword", "global.s3url", "global.egaUser", + "global.iss", "global.crypt4ghKey", "global.uppmaxUsername", "global.uppmaxPassword", "global.s3url", "global.egaUser", "global.jwtKey", } for _, s := range requiredConfVars { @@ -74,19 +74,19 @@ func NewConf(conf *Conf) (err error) { } conf.Iss = viper.GetString("global.iss") - conf.PathToKey = viper.GetString("global.pathToKey") + conf.JwtKeyPath = viper.GetString("global.jwtKey") conf.Username = viper.GetString("global.uppmaxUsername") conf.Password = viper.GetString("global.uppmaxPassword") conf.S3URL = viper.GetString("global.s3url") conf.EgaUser = viper.GetString("global.egaUser") - conf.Crypt4ghKeyPath = viper.GetString("global.crypt4ghKeyPath") + conf.Crypt4ghKeyPath = viper.GetString("global.crypt4ghKey") if !viper.IsSet("global.expirationDays") { conf.ExpirationDays = 14 } else { conf.ExpirationDays = viper.GetInt("global.expirationDays") } - conf.ParsedKey, err = parsePrivateECKey(conf.PathToKey) + conf.JwtParsedKey, err = parsePrivateECKey(conf.JwtKeyPath) if err != nil { return fmt.Errorf("Could not parse ec key") } diff --git a/helpers/helpers_test.go b/helpers/helpers_test.go index bdfab30..ee07bbd 100644 --- a/helpers/helpers_test.go +++ b/helpers/helpers_test.go @@ -50,13 +50,13 @@ func (suite *TestSuite) TestCreateErrorResponse() { func (suite *TestSuite) TestNewConf() { confData := `global: iss: "https://some.url" - pathToKey: "` + suite.PrivateKeyPath + `" + jwtKey: "` + suite.PrivateKeyPath + `" uppmaxUsername: "user" uppmaxPassword: "password" s3url: "some.s3.url" expirationDays: 14 egaUser: "some-user" - crypt4ghKeyPath: "` + suite.Crypt4ghKeyPath + `" + crypt4ghKey: "` + suite.Crypt4ghKeyPath + `" ` configName := "config.yaml" @@ -73,13 +73,13 @@ func (suite *TestSuite) TestNewConf() { func (suite *TestSuite) TestNewConfMissingValue() { confData := `global: - pathToKey: "` + suite.PrivateKeyPath + `" + jwtKey: "` + suite.PrivateKeyPath + `" uppmaxUsername: "user" uppmaxPassword: "password" s3url: "some.s3.url" expirationDays: 14 egaUser: "some-user" - crypt4ghKeyPath: "` + suite.Crypt4ghKeyPath + `" + crypt4ghKey: "` + suite.Crypt4ghKeyPath + `" ` configName := "config.yaml" @@ -97,13 +97,13 @@ func (suite *TestSuite) TestNewConfMissingValue() { func (suite *TestSuite) TestNewConfMissingKey() { confData := `global: iss: "https://some.url" - pathToKey: "some/path" + jwtKey: "some/path" uppmaxUsername: "user" uppmaxPassword: "password" s3url: "some.s3.url" expirationDays: 14 egaUser: "some-user" - crypt4ghKeyPath: "` + suite.Crypt4ghKeyPath + `" + crypt4ghKey: "` + suite.Crypt4ghKeyPath + `" ` configName := "config.yaml" err := ioutil.WriteFile(configName, []byte(confData), 0600) diff --git a/token/token.go b/token/token.go index 99c2192..2124ef4 100644 --- a/token/token.go +++ b/token/token.go @@ -82,7 +82,7 @@ func createS3Config(username string) (s3config string, expiration string, err er "check_ssl_hostname = True\n" + "encoding = UTF-8\n" + "encrypt = False\n" + "socket_timeout = 30\n" - token, err := createECToken(helpers.Config.ParsedKey, username) + token, err := createECToken(helpers.Config.JwtParsedKey, username) if err != nil { return "", "", err } diff --git a/token/token_test.go b/token/token_test.go index e0a0862..9771b17 100644 --- a/token/token_test.go +++ b/token/token_test.go @@ -82,13 +82,13 @@ func (suite *TestSuite) TestCreateECToken() { confData := `global: iss: "https://some.url" - pathToKey: "` + suite.PrivateKeyPath + `" + jwtKey: "` + suite.PrivateKeyPath + `" uppmaxUsername: "user" uppmaxPassword: "password" s3url: "some.s3.url" expirationDays: 14 egaUser: "some-user" - crypt4ghKeyPath: "` + suite.Crypt4ghKeyPath + `" + crypt4ghKey: "` + suite.Crypt4ghKeyPath + `" ` configName := "config.yaml" err := ioutil.WriteFile(configName, []byte(confData), 0600) @@ -99,7 +99,7 @@ func (suite *TestSuite) TestCreateECToken() { err = helpers.NewConf(&helpers.Config) assert.NoError(suite.T(), err) - tokenString, err := createECToken(helpers.Config.ParsedKey, helpers.Config.EgaUser) + tokenString, err := createECToken(helpers.Config.JwtParsedKey, helpers.Config.EgaUser) assert.NoError(suite.T(), err) // Parse token to make sure it contains the correct information @@ -131,13 +131,13 @@ func (suite *TestSuite) TestCreateResponse() { confData := `global: iss: "https://some.url" - pathToKey: "` + suite.PrivateKeyPath + `" + jwtKey: "` + suite.PrivateKeyPath + `" uppmaxUsername: "user" uppmaxPassword: "password" s3url: "some.s3.url" expirationDays: 14 egaUser: "some-user" - crypt4ghKeyPath: "` + suite.Crypt4ghKeyPath + `" + crypt4ghKey: "` + suite.Crypt4ghKeyPath + `" ` configName := "config.yaml" err := ioutil.WriteFile(configName, []byte(confData), 0600)