Add support for self-signed CA certificates (#2268)
Co-authored-by: Bassem Dghaidi <568794+Link-@users.noreply.github.com> Co-authored-by: Nikola Jokic <jokicnikola07@gmail.com> Co-authored-by: Tingluo Huang <tingluohuang@github.com>
This commit is contained in:
parent
068f987238
commit
c569304271
|
|
@ -57,6 +57,9 @@ type AutoscalingListenerSpec struct {
|
||||||
|
|
||||||
// +optional
|
// +optional
|
||||||
Proxy *ProxyConfig `json:"proxy,omitempty"`
|
Proxy *ProxyConfig `json:"proxy,omitempty"`
|
||||||
|
|
||||||
|
// +optional
|
||||||
|
GitHubServerTLS *GitHubServerTLSConfig `json:"githubServerTLS,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AutoscalingListenerStatus defines the observed state of AutoscalingListener
|
// AutoscalingListenerStatus defines the observed state of AutoscalingListener
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
@ -80,7 +81,44 @@ type AutoscalingRunnerSetSpec struct {
|
||||||
|
|
||||||
type GitHubServerTLSConfig struct {
|
type GitHubServerTLSConfig struct {
|
||||||
// Required
|
// Required
|
||||||
RootCAsConfigMapRef string `json:"certConfigMapRef,omitempty"`
|
CertificateFrom *TLSCertificateSource `json:"certificateFrom,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *GitHubServerTLSConfig) ToCertPool(keyFetcher func(name, key string) ([]byte, error)) (*x509.CertPool, error) {
|
||||||
|
if c.CertificateFrom == nil {
|
||||||
|
return nil, fmt.Errorf("certificateFrom not specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.CertificateFrom.ConfigMapKeyRef == nil {
|
||||||
|
return nil, fmt.Errorf("configMapKeyRef not specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := keyFetcher(c.CertificateFrom.ConfigMapKeyRef.Name, c.CertificateFrom.ConfigMapKeyRef.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"failed to fetch key %q in configmap %q: %w",
|
||||||
|
c.CertificateFrom.ConfigMapKeyRef.Key,
|
||||||
|
c.CertificateFrom.ConfigMapKeyRef.Name,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
systemPool, err := x509.SystemCertPool()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get system cert pool: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pool := systemPool.Clone()
|
||||||
|
if !pool.AppendCertsFromPEM(cert) {
|
||||||
|
return nil, fmt.Errorf("failed to parse certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
return pool, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TLSCertificateSource struct {
|
||||||
|
// Required
|
||||||
|
ConfigMapKeyRef *corev1.ConfigMapKeySelector `json:"configMapKeyRef,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyConfig struct {
|
type ProxyConfig struct {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
package v1alpha1_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
||||||
|
"github.com/actions/actions-runner-controller/github/actions/testserver"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGitHubServerTLSConfig_ToCertPool(t *testing.T) {
|
||||||
|
t.Run("returns an error if CertificateFrom not specified", func(t *testing.T) {
|
||||||
|
c := &v1alpha1.GitHubServerTLSConfig{
|
||||||
|
CertificateFrom: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
pool, err := c.ToCertPool(nil)
|
||||||
|
assert.Nil(t, pool)
|
||||||
|
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Equal(t, err.Error(), "certificateFrom not specified")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns an error if CertificateFrom.ConfigMapKeyRef not specified", func(t *testing.T) {
|
||||||
|
c := &v1alpha1.GitHubServerTLSConfig{
|
||||||
|
CertificateFrom: &v1alpha1.TLSCertificateSource{},
|
||||||
|
}
|
||||||
|
|
||||||
|
pool, err := c.ToCertPool(nil)
|
||||||
|
assert.Nil(t, pool)
|
||||||
|
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Equal(t, err.Error(), "configMapKeyRef not specified")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("returns a valid cert pool with correct configuration", func(t *testing.T) {
|
||||||
|
c := &v1alpha1.GitHubServerTLSConfig{
|
||||||
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
|
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
|
||||||
|
LocalObjectReference: v1.LocalObjectReference{
|
||||||
|
Name: "name",
|
||||||
|
},
|
||||||
|
Key: "key",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
certsFolder := filepath.Join(
|
||||||
|
"../../../",
|
||||||
|
"github",
|
||||||
|
"actions",
|
||||||
|
"testdata",
|
||||||
|
)
|
||||||
|
|
||||||
|
fetcher := func(name, key string) ([]byte, error) {
|
||||||
|
cert, err := os.ReadFile(filepath.Join(certsFolder, "rootCA.crt"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pool := x509.NewCertPool()
|
||||||
|
ok := pool.AppendCertsFromPEM(cert)
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
return cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pool, err := c.ToCertPool(fetcher)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, pool)
|
||||||
|
|
||||||
|
// can be used to communicate with a server
|
||||||
|
serverSuccessfullyCalled := false
|
||||||
|
server := testserver.NewUnstarted(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
serverSuccessfullyCalled = true
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
|
||||||
|
cert, err := tls.LoadX509KeyPair(
|
||||||
|
filepath.Join(certsFolder, "server.crt"),
|
||||||
|
filepath.Join(certsFolder, "server.key"),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||||
|
server.StartTLS()
|
||||||
|
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
RootCAs: pool,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.Get(server.URL)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, serverSuccessfullyCalled)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -98,6 +98,11 @@ func (in *AutoscalingListenerSpec) DeepCopyInto(out *AutoscalingListenerSpec) {
|
||||||
*out = new(ProxyConfig)
|
*out = new(ProxyConfig)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
|
if in.GitHubServerTLS != nil {
|
||||||
|
in, out := &in.GitHubServerTLS, &out.GitHubServerTLS
|
||||||
|
*out = new(GitHubServerTLSConfig)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoscalingListenerSpec.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutoscalingListenerSpec.
|
||||||
|
|
@ -195,7 +200,7 @@ func (in *AutoscalingRunnerSetSpec) DeepCopyInto(out *AutoscalingRunnerSetSpec)
|
||||||
if in.GitHubServerTLS != nil {
|
if in.GitHubServerTLS != nil {
|
||||||
in, out := &in.GitHubServerTLS, &out.GitHubServerTLS
|
in, out := &in.GitHubServerTLS, &out.GitHubServerTLS
|
||||||
*out = new(GitHubServerTLSConfig)
|
*out = new(GitHubServerTLSConfig)
|
||||||
**out = **in
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
in.Template.DeepCopyInto(&out.Template)
|
in.Template.DeepCopyInto(&out.Template)
|
||||||
if in.MaxRunners != nil {
|
if in.MaxRunners != nil {
|
||||||
|
|
@ -395,7 +400,7 @@ func (in *EphemeralRunnerSpec) DeepCopyInto(out *EphemeralRunnerSpec) {
|
||||||
if in.GitHubServerTLS != nil {
|
if in.GitHubServerTLS != nil {
|
||||||
in, out := &in.GitHubServerTLS, &out.GitHubServerTLS
|
in, out := &in.GitHubServerTLS, &out.GitHubServerTLS
|
||||||
*out = new(GitHubServerTLSConfig)
|
*out = new(GitHubServerTLSConfig)
|
||||||
**out = **in
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
in.PodTemplateSpec.DeepCopyInto(&out.PodTemplateSpec)
|
in.PodTemplateSpec.DeepCopyInto(&out.PodTemplateSpec)
|
||||||
}
|
}
|
||||||
|
|
@ -435,6 +440,11 @@ func (in *EphemeralRunnerStatus) DeepCopy() *EphemeralRunnerStatus {
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *GitHubServerTLSConfig) DeepCopyInto(out *GitHubServerTLSConfig) {
|
func (in *GitHubServerTLSConfig) DeepCopyInto(out *GitHubServerTLSConfig) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
if in.CertificateFrom != nil {
|
||||||
|
in, out := &in.CertificateFrom, &out.CertificateFrom
|
||||||
|
*out = new(TLSCertificateSource)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitHubServerTLSConfig.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitHubServerTLSConfig.
|
||||||
|
|
@ -491,3 +501,23 @@ func (in *ProxyServerConfig) DeepCopy() *ProxyServerConfig {
|
||||||
in.DeepCopyInto(out)
|
in.DeepCopyInto(out)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *TLSCertificateSource) DeepCopyInto(out *TLSCertificateSource) {
|
||||||
|
*out = *in
|
||||||
|
if in.ConfigMapKeyRef != nil {
|
||||||
|
in, out := &in.ConfigMapKeyRef, &out.ConfigMapKeyRef
|
||||||
|
*out = new(v1.ConfigMapKeySelector)
|
||||||
|
(*in).DeepCopyInto(*out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSCertificateSource.
|
||||||
|
func (in *TLSCertificateSource) DeepCopy() *TLSCertificateSource {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(TLSCertificateSource)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,28 @@ spec:
|
||||||
githubConfigUrl:
|
githubConfigUrl:
|
||||||
description: Required
|
description: Required
|
||||||
type: string
|
type: string
|
||||||
|
githubServerTLS:
|
||||||
|
properties:
|
||||||
|
certificateFrom:
|
||||||
|
description: Required
|
||||||
|
properties:
|
||||||
|
configMapKeyRef:
|
||||||
|
description: Required
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
description: The key to select.
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||||
|
type: string
|
||||||
|
optional:
|
||||||
|
description: Specify whether the ConfigMap or its key must be defined
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
image:
|
image:
|
||||||
description: Required
|
description: Required
|
||||||
type: string
|
type: string
|
||||||
|
|
|
||||||
|
|
@ -51,9 +51,25 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
githubServerTLS:
|
githubServerTLS:
|
||||||
properties:
|
properties:
|
||||||
certConfigMapRef:
|
certificateFrom:
|
||||||
description: Required
|
description: Required
|
||||||
type: string
|
properties:
|
||||||
|
configMapKeyRef:
|
||||||
|
description: Required
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
description: The key to select.
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||||
|
type: string
|
||||||
|
optional:
|
||||||
|
description: Specify whether the ConfigMap or its key must be defined
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
type: object
|
type: object
|
||||||
maxRunners:
|
maxRunners:
|
||||||
minimum: 0
|
minimum: 0
|
||||||
|
|
|
||||||
|
|
@ -64,9 +64,25 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
githubServerTLS:
|
githubServerTLS:
|
||||||
properties:
|
properties:
|
||||||
certConfigMapRef:
|
certificateFrom:
|
||||||
description: Required
|
description: Required
|
||||||
type: string
|
properties:
|
||||||
|
configMapKeyRef:
|
||||||
|
description: Required
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
description: The key to select.
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||||
|
type: string
|
||||||
|
optional:
|
||||||
|
description: Specify whether the ConfigMap or its key must be defined
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
type: object
|
type: object
|
||||||
metadata:
|
metadata:
|
||||||
description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata'
|
description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata'
|
||||||
|
|
|
||||||
|
|
@ -46,9 +46,25 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
githubServerTLS:
|
githubServerTLS:
|
||||||
properties:
|
properties:
|
||||||
certConfigMapRef:
|
certificateFrom:
|
||||||
description: Required
|
description: Required
|
||||||
type: string
|
properties:
|
||||||
|
configMapKeyRef:
|
||||||
|
description: Required
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
description: The key to select.
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||||
|
type: string
|
||||||
|
optional:
|
||||||
|
description: Specify whether the ConfigMap or its key must be defined
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
type: object
|
type: object
|
||||||
metadata:
|
metadata:
|
||||||
description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata'
|
description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata'
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,13 @@ rules:
|
||||||
- get
|
- get
|
||||||
- list
|
- list
|
||||||
- watch
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- configmaps
|
||||||
|
verbs:
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
- apiGroups:
|
- apiGroups:
|
||||||
- rbac.authorization.k8s.io
|
- rbac.authorization.k8s.io
|
||||||
resources:
|
resources:
|
||||||
|
|
@ -167,4 +174,4 @@ rules:
|
||||||
- get
|
- get
|
||||||
- update
|
- update
|
||||||
- list
|
- list
|
||||||
- watch
|
- watch
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ func TestTemplate_CreateManagerRole(t *testing.T) {
|
||||||
|
|
||||||
assert.Empty(t, managerRole.Namespace, "ClusterRole should not have a namespace")
|
assert.Empty(t, managerRole.Namespace, "ClusterRole should not have a namespace")
|
||||||
assert.Equal(t, "test-arc-gha-runner-scale-set-controller-manager-role", managerRole.Name)
|
assert.Equal(t, "test-arc-gha-runner-scale-set-controller-manager-role", managerRole.Name)
|
||||||
assert.Equal(t, 17, len(managerRole.Rules))
|
assert.Equal(t, 18, len(managerRole.Rules))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemplate_ManagerRoleBinding(t *testing.T) {
|
func TestTemplate_ManagerRoleBinding(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,15 @@ volumeMounts:
|
||||||
emptyDir: {}
|
emptyDir: {}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "gha-runner-scale-set.tls-volume" -}}
|
||||||
|
- name: github-server-tls-cert
|
||||||
|
configMap:
|
||||||
|
name: {{ .certificateFrom.configMapKeyRef.name }}
|
||||||
|
items:
|
||||||
|
- key: {{ .certificateFrom.configMapKeyRef.key }}
|
||||||
|
path: {{ .certificateFrom.configMapKeyRef.key }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
{{- define "gha-runner-scale-set.dind-work-volume" -}}
|
{{- define "gha-runner-scale-set.dind-work-volume" -}}
|
||||||
{{- $createWorkVolume := 1 }}
|
{{- $createWorkVolume := 1 }}
|
||||||
{{- range $i, $volume := .Values.template.spec.volumes }}
|
{{- range $i, $volume := .Values.template.spec.volumes }}
|
||||||
|
|
@ -155,12 +164,7 @@ volumeMounts:
|
||||||
{{- define "gha-runner-scale-set.non-work-volumes" -}}
|
{{- define "gha-runner-scale-set.non-work-volumes" -}}
|
||||||
{{- range $i, $volume := .Values.template.spec.volumes }}
|
{{- range $i, $volume := .Values.template.spec.volumes }}
|
||||||
{{- if ne $volume.name "work" }}
|
{{- if ne $volume.name "work" }}
|
||||||
- name: {{ $volume.name }}
|
- {{ $volume | toYaml | nindent 2 }}
|
||||||
{{- range $key, $val := $volume }}
|
|
||||||
{{- if ne $key "name" }}
|
|
||||||
{{ $key }}: {{ $val }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
@ -179,6 +183,7 @@ volumeMounts:
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{- define "gha-runner-scale-set.dind-runner-container" -}}
|
{{- define "gha-runner-scale-set.dind-runner-container" -}}
|
||||||
|
{{- $tlsConfig := (default (dict) .Values.githubServerTLS) }}
|
||||||
{{- range $i, $container := .Values.template.spec.containers -}}
|
{{- range $i, $container := .Values.template.spec.containers -}}
|
||||||
{{- if eq $container.name "runner" -}}
|
{{- if eq $container.name "runner" -}}
|
||||||
{{- range $key, $val := $container }}
|
{{- range $key, $val := $container }}
|
||||||
|
|
@ -190,6 +195,12 @@ volumeMounts:
|
||||||
{{- $setDockerTlsVerify := 1 }}
|
{{- $setDockerTlsVerify := 1 }}
|
||||||
{{- $setDockerCertPath := 1 }}
|
{{- $setDockerCertPath := 1 }}
|
||||||
{{- $setRunnerWaitDocker := 1 }}
|
{{- $setRunnerWaitDocker := 1 }}
|
||||||
|
{{- $setNodeExtraCaCerts := 0 }}
|
||||||
|
{{- $setRunnerUpdateCaCerts := 0 }}
|
||||||
|
{{- if $tlsConfig.runnerMountPath }}
|
||||||
|
{{- $setNodeExtraCaCerts = 1 }}
|
||||||
|
{{- $setRunnerUpdateCaCerts = 1 }}
|
||||||
|
{{- end }}
|
||||||
env:
|
env:
|
||||||
{{- with $container.env }}
|
{{- with $container.env }}
|
||||||
{{- range $i, $env := . }}
|
{{- range $i, $env := . }}
|
||||||
|
|
@ -205,6 +216,12 @@ env:
|
||||||
{{- if eq $env.name "RUNNER_WAIT_FOR_DOCKER_IN_SECONDS" }}
|
{{- if eq $env.name "RUNNER_WAIT_FOR_DOCKER_IN_SECONDS" }}
|
||||||
{{- $setRunnerWaitDocker = 0 -}}
|
{{- $setRunnerWaitDocker = 0 -}}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- if eq $env.name "NODE_EXTRA_CA_CERTS" }}
|
||||||
|
{{- $setNodeExtraCaCerts = 0 -}}
|
||||||
|
{{- end }}
|
||||||
|
{{- if eq $env.name "RUNNER_UPDATE_CA_CERTS" }}
|
||||||
|
{{- $setRunnerUpdateCaCerts = 0 -}}
|
||||||
|
{{- end }}
|
||||||
- name: {{ $env.name }}
|
- name: {{ $env.name }}
|
||||||
{{- range $envKey, $envVal := $env }}
|
{{- range $envKey, $envVal := $env }}
|
||||||
{{- if ne $envKey "name" }}
|
{{- if ne $envKey "name" }}
|
||||||
|
|
@ -229,8 +246,20 @@ env:
|
||||||
- name: RUNNER_WAIT_FOR_DOCKER_IN_SECONDS
|
- name: RUNNER_WAIT_FOR_DOCKER_IN_SECONDS
|
||||||
value: "120"
|
value: "120"
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- if $setNodeExtraCaCerts }}
|
||||||
|
- name: NODE_EXTRA_CA_CERTS
|
||||||
|
value: {{ clean (print $tlsConfig.runnerMountPath "/" $tlsConfig.certificateFrom.configMapKeyRef.key) }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if $setRunnerUpdateCaCerts }}
|
||||||
|
- name: RUNNER_UPDATE_CA_CERTS
|
||||||
|
value: "1"
|
||||||
|
{{- end }}
|
||||||
{{- $mountWork := 1 }}
|
{{- $mountWork := 1 }}
|
||||||
{{- $mountDindCert := 1 }}
|
{{- $mountDindCert := 1 }}
|
||||||
|
{{- $mountGitHubServerTLS := 0 }}
|
||||||
|
{{- if $tlsConfig.runnerMountPath }}
|
||||||
|
{{- $mountGitHubServerTLS = 1 }}
|
||||||
|
{{- end }}
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
{{- with $container.volumeMounts }}
|
{{- with $container.volumeMounts }}
|
||||||
{{- range $i, $volMount := . }}
|
{{- range $i, $volMount := . }}
|
||||||
|
|
@ -240,6 +269,9 @@ volumeMounts:
|
||||||
{{- if eq $volMount.name "dind-cert" }}
|
{{- if eq $volMount.name "dind-cert" }}
|
||||||
{{- $mountDindCert = 0 -}}
|
{{- $mountDindCert = 0 -}}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- if eq $volMount.name "github-server-tls-cert" }}
|
||||||
|
{{- $mountGitHubServerTLS = 0 -}}
|
||||||
|
{{- end }}
|
||||||
- name: {{ $volMount.name }}
|
- name: {{ $volMount.name }}
|
||||||
{{- range $mountKey, $mountVal := $volMount }}
|
{{- range $mountKey, $mountVal := $volMount }}
|
||||||
{{- if ne $mountKey "name" }}
|
{{- if ne $mountKey "name" }}
|
||||||
|
|
@ -257,11 +289,17 @@ volumeMounts:
|
||||||
mountPath: /certs/client
|
mountPath: /certs/client
|
||||||
readOnly: true
|
readOnly: true
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- if $mountGitHubServerTLS }}
|
||||||
|
- name: github-server-tls-cert
|
||||||
|
mountPath: {{ clean (print $tlsConfig.runnerMountPath "/" $tlsConfig.certificateFrom.configMapKeyRef.key) }}
|
||||||
|
subPath: {{ $tlsConfig.certificateFrom.configMapKeyRef.key }}
|
||||||
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{- define "gha-runner-scale-set.kubernetes-mode-runner-container" -}}
|
{{- define "gha-runner-scale-set.kubernetes-mode-runner-container" -}}
|
||||||
|
{{- $tlsConfig := (default (dict) .Values.githubServerTLS) }}
|
||||||
{{- range $i, $container := .Values.template.spec.containers -}}
|
{{- range $i, $container := .Values.template.spec.containers -}}
|
||||||
{{- if eq $container.name "runner" -}}
|
{{- if eq $container.name "runner" -}}
|
||||||
{{- range $key, $val := $container }}
|
{{- range $key, $val := $container }}
|
||||||
|
|
@ -272,6 +310,12 @@ volumeMounts:
|
||||||
{{- $setContainerHooks := 1 }}
|
{{- $setContainerHooks := 1 }}
|
||||||
{{- $setPodName := 1 }}
|
{{- $setPodName := 1 }}
|
||||||
{{- $setRequireJobContainer := 1 }}
|
{{- $setRequireJobContainer := 1 }}
|
||||||
|
{{- $setNodeExtraCaCerts := 0 }}
|
||||||
|
{{- $setRunnerUpdateCaCerts := 0 }}
|
||||||
|
{{- if $tlsConfig.runnerMountPath }}
|
||||||
|
{{- $setNodeExtraCaCerts = 1 }}
|
||||||
|
{{- $setRunnerUpdateCaCerts = 1 }}
|
||||||
|
{{- end }}
|
||||||
env:
|
env:
|
||||||
{{- with $container.env }}
|
{{- with $container.env }}
|
||||||
{{- range $i, $env := . }}
|
{{- range $i, $env := . }}
|
||||||
|
|
@ -284,6 +328,12 @@ env:
|
||||||
{{- if eq $env.name "ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER" }}
|
{{- if eq $env.name "ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER" }}
|
||||||
{{- $setRequireJobContainer = 0 -}}
|
{{- $setRequireJobContainer = 0 -}}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- if eq $env.name "NODE_EXTRA_CA_CERTS" }}
|
||||||
|
{{- $setNodeExtraCaCerts = 0 -}}
|
||||||
|
{{- end }}
|
||||||
|
{{- if eq $env.name "RUNNER_UPDATE_CA_CERTS" }}
|
||||||
|
{{- $setRunnerUpdateCaCerts = 0 -}}
|
||||||
|
{{- end }}
|
||||||
- name: {{ $env.name }}
|
- name: {{ $env.name }}
|
||||||
{{- range $envKey, $envVal := $env }}
|
{{- range $envKey, $envVal := $env }}
|
||||||
{{- if ne $envKey "name" }}
|
{{- if ne $envKey "name" }}
|
||||||
|
|
@ -306,13 +356,28 @@ env:
|
||||||
- name: ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER
|
- name: ACTIONS_RUNNER_REQUIRE_JOB_CONTAINER
|
||||||
value: "true"
|
value: "true"
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- if $setNodeExtraCaCerts }}
|
||||||
|
- name: NODE_EXTRA_CA_CERTS
|
||||||
|
value: {{ clean (print $tlsConfig.runnerMountPath "/" $tlsConfig.certificateFrom.configMapKeyRef.key) }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if $setRunnerUpdateCaCerts }}
|
||||||
|
- name: RUNNER_UPDATE_CA_CERTS
|
||||||
|
value: "1"
|
||||||
|
{{- end }}
|
||||||
{{- $mountWork := 1 }}
|
{{- $mountWork := 1 }}
|
||||||
|
{{- $mountGitHubServerTLS := 0 }}
|
||||||
|
{{- if $tlsConfig.runnerMountPath }}
|
||||||
|
{{- $mountGitHubServerTLS = 1 }}
|
||||||
|
{{- end }}
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
{{- with $container.volumeMounts }}
|
{{- with $container.volumeMounts }}
|
||||||
{{- range $i, $volMount := . }}
|
{{- range $i, $volMount := . }}
|
||||||
{{- if eq $volMount.name "work" }}
|
{{- if eq $volMount.name "work" }}
|
||||||
{{- $mountWork = 0 -}}
|
{{- $mountWork = 0 -}}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- if eq $volMount.name "github-server-tls-cert" }}
|
||||||
|
{{- $mountGitHubServerTLS = 0 -}}
|
||||||
|
{{- end }}
|
||||||
- name: {{ $volMount.name }}
|
- name: {{ $volMount.name }}
|
||||||
{{- range $mountKey, $mountVal := $volMount }}
|
{{- range $mountKey, $mountVal := $volMount }}
|
||||||
{{- if ne $mountKey "name" }}
|
{{- if ne $mountKey "name" }}
|
||||||
|
|
@ -325,6 +390,81 @@ volumeMounts:
|
||||||
- name: work
|
- name: work
|
||||||
mountPath: /actions-runner/_work
|
mountPath: /actions-runner/_work
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- if $mountGitHubServerTLS }}
|
||||||
|
- name: github-server-tls-cert
|
||||||
|
mountPath: {{ clean (print $tlsConfig.runnerMountPath "/" $tlsConfig.certificateFrom.configMapKeyRef.key) }}
|
||||||
|
subPath: {{ $tlsConfig.certificateFrom.configMapKeyRef.key }}
|
||||||
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "gha-runner-scale-set.default-mode-runner-containers" -}}
|
||||||
|
{{- $tlsConfig := (default (dict) .Values.githubServerTLS) }}
|
||||||
|
{{- range $i, $container := .Values.template.spec.containers -}}
|
||||||
|
{{- if ne $container.name "runner" -}}
|
||||||
|
- {{ $container | toYaml | nindent 2 }}
|
||||||
|
{{- else }}
|
||||||
|
- name: {{ $container.name }}
|
||||||
|
{{- range $key, $val := $container }}
|
||||||
|
{{- if and (ne $key "env") (ne $key "volumeMounts") (ne $key "name") }}
|
||||||
|
{{ $key }}: {{ $val }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- $setNodeExtraCaCerts := 0 }}
|
||||||
|
{{- $setRunnerUpdateCaCerts := 0 }}
|
||||||
|
{{- if $tlsConfig.runnerMountPath }}
|
||||||
|
{{- $setNodeExtraCaCerts = 1 }}
|
||||||
|
{{- $setRunnerUpdateCaCerts = 1 }}
|
||||||
|
{{- end }}
|
||||||
|
env:
|
||||||
|
{{- with $container.env }}
|
||||||
|
{{- range $i, $env := . }}
|
||||||
|
{{- if eq $env.name "NODE_EXTRA_CA_CERTS" }}
|
||||||
|
{{- $setNodeExtraCaCerts = 0 -}}
|
||||||
|
{{- end }}
|
||||||
|
{{- if eq $env.name "RUNNER_UPDATE_CA_CERTS" }}
|
||||||
|
{{- $setRunnerUpdateCaCerts = 0 -}}
|
||||||
|
{{- end }}
|
||||||
|
- name: {{ $env.name }}
|
||||||
|
{{- range $envKey, $envVal := $env }}
|
||||||
|
{{- if ne $envKey "name" }}
|
||||||
|
{{ $envKey }}: {{ $envVal | toYaml | nindent 10 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if $setNodeExtraCaCerts }}
|
||||||
|
- name: NODE_EXTRA_CA_CERTS
|
||||||
|
value: {{ clean (print $tlsConfig.runnerMountPath "/" $tlsConfig.certificateFrom.configMapKeyRef.key) }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if $setRunnerUpdateCaCerts }}
|
||||||
|
- name: RUNNER_UPDATE_CA_CERTS
|
||||||
|
value: "1"
|
||||||
|
{{- end }}
|
||||||
|
{{- $mountGitHubServerTLS := 0 }}
|
||||||
|
{{- if $tlsConfig.runnerMountPath }}
|
||||||
|
{{- $mountGitHubServerTLS = 1 }}
|
||||||
|
{{- end }}
|
||||||
|
volumeMounts:
|
||||||
|
{{- with $container.volumeMounts }}
|
||||||
|
{{- range $i, $volMount := . }}
|
||||||
|
{{- if eq $volMount.name "github-server-tls-cert" }}
|
||||||
|
{{- $mountGitHubServerTLS = 0 -}}
|
||||||
|
{{- end }}
|
||||||
|
- name: {{ $volMount.name }}
|
||||||
|
{{- range $mountKey, $mountVal := $volMount }}
|
||||||
|
{{- if ne $mountKey "name" }}
|
||||||
|
{{ $mountKey }}: {{ $mountVal | toYaml | nindent 10 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if $mountGitHubServerTLS }}
|
||||||
|
- name: github-server-tls-cert
|
||||||
|
mountPath: {{ clean (print $tlsConfig.runnerMountPath "/" $tlsConfig.certificateFrom.configMapKeyRef.key) }}
|
||||||
|
subPath: {{ $tlsConfig.certificateFrom.configMapKeyRef.key }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,16 @@ spec:
|
||||||
runnerScaleSetName: {{ . }}
|
runnerScaleSetName: {{ . }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
|
{{- if .Values.githubServerTLS }}
|
||||||
|
githubServerTLS:
|
||||||
|
{{- with .Values.githubServerTLS.certificateFrom }}
|
||||||
|
certificateFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: {{ .configMapKeyRef.name }}
|
||||||
|
key: {{ .configMapKeyRef.key }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
{{- if .Values.proxy }}
|
{{- if .Values.proxy }}
|
||||||
proxy:
|
proxy:
|
||||||
{{- if .Values.proxy.http }}
|
{{- if .Values.proxy.http }}
|
||||||
|
|
@ -103,10 +113,14 @@ spec:
|
||||||
{{- include "gha-runner-scale-set.kubernetes-mode-runner-container" . | nindent 8 }}
|
{{- include "gha-runner-scale-set.kubernetes-mode-runner-container" . | nindent 8 }}
|
||||||
{{- include "gha-runner-scale-set.non-runner-containers" . | nindent 6 }}
|
{{- include "gha-runner-scale-set.non-runner-containers" . | nindent 6 }}
|
||||||
{{- else }}
|
{{- else }}
|
||||||
{{ .Values.template.spec.containers | toYaml | nindent 6 }}
|
{{- include "gha-runner-scale-set.default-mode-runner-containers" . | nindent 6 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if or .Values.template.spec.volumes (eq .Values.containerMode.type "dind") (eq .Values.containerMode.type "kubernetes") }}
|
{{- $tlsConfig := (default (dict) .Values.githubServerTLS) }}
|
||||||
|
{{- if or .Values.template.spec.volumes (eq .Values.containerMode.type "dind") (eq .Values.containerMode.type "kubernetes") $tlsConfig.runnerMountPath }}
|
||||||
volumes:
|
volumes:
|
||||||
|
{{- if $tlsConfig.runnerMountPath }}
|
||||||
|
{{- include "gha-runner-scale-set.tls-volume" $tlsConfig | nindent 6 }}
|
||||||
|
{{- end }}
|
||||||
{{- if eq .Values.containerMode.type "dind" }}
|
{{- if eq .Values.containerMode.type "dind" }}
|
||||||
{{- include "gha-runner-scale-set.dind-volume" . | nindent 6 }}
|
{{- include "gha-runner-scale-set.dind-volume" . | nindent 6 }}
|
||||||
{{- include "gha-runner-scale-set.dind-work-volume" . | nindent 6 }}
|
{{- include "gha-runner-scale-set.dind-work-volume" . | nindent 6 }}
|
||||||
|
|
|
||||||
|
|
@ -828,6 +828,365 @@ func TestTemplateRenderedWithProxy(t *testing.T) {
|
||||||
assert.Contains(t, ars.Spec.Proxy.NoProxy, "example.org")
|
assert.Contains(t, ars.Spec.Proxy.NoProxy, "example.org")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTemplateRenderedWithTLS(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
namespaceName := "test-" + strings.ToLower(random.UniqueId())
|
||||||
|
|
||||||
|
render := func(t *testing.T, options *helm.Options) v1alpha1.AutoscalingRunnerSet {
|
||||||
|
// Path to the helm chart we will test
|
||||||
|
helmChartPath, err := filepath.Abs("../../gha-runner-scale-set")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
releaseName := "test-runners"
|
||||||
|
|
||||||
|
output := helm.RenderTemplate(
|
||||||
|
t,
|
||||||
|
options,
|
||||||
|
helmChartPath,
|
||||||
|
releaseName,
|
||||||
|
[]string{"templates/autoscalingrunnerset.yaml"},
|
||||||
|
)
|
||||||
|
|
||||||
|
var ars v1alpha1.AutoscalingRunnerSet
|
||||||
|
helm.UnmarshalK8SYaml(t, output, &ars)
|
||||||
|
|
||||||
|
return ars
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("providing githubServerTLS.runnerMountPath", func(t *testing.T) {
|
||||||
|
t.Run("mode: default", func(t *testing.T) {
|
||||||
|
options := &helm.Options{
|
||||||
|
SetValues: map[string]string{
|
||||||
|
"githubConfigUrl": "https://github.com/actions",
|
||||||
|
"githubConfigSecret": "pre-defined-secrets",
|
||||||
|
"githubServerTLS.certificateFrom.configMapKeyRef.name": "certs-configmap",
|
||||||
|
"githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem",
|
||||||
|
"githubServerTLS.runnerMountPath": "/runner/mount/path",
|
||||||
|
},
|
||||||
|
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
|
||||||
|
}
|
||||||
|
|
||||||
|
ars := render(t, options)
|
||||||
|
|
||||||
|
require.NotNil(t, ars.Spec.GitHubServerTLS)
|
||||||
|
expected := &v1alpha1.GitHubServerTLSConfig{
|
||||||
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
Name: "certs-configmap",
|
||||||
|
},
|
||||||
|
Key: "cert.pem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, expected, ars.Spec.GitHubServerTLS)
|
||||||
|
|
||||||
|
var volume *corev1.Volume
|
||||||
|
for _, v := range ars.Spec.Template.Spec.Volumes {
|
||||||
|
if v.Name == "github-server-tls-cert" {
|
||||||
|
volume = &v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.NotNil(t, volume)
|
||||||
|
assert.Equal(t, "certs-configmap", volume.ConfigMap.LocalObjectReference.Name)
|
||||||
|
assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Key)
|
||||||
|
assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Path)
|
||||||
|
|
||||||
|
assert.Contains(t, ars.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
|
||||||
|
Name: "github-server-tls-cert",
|
||||||
|
MountPath: "/runner/mount/path/cert.pem",
|
||||||
|
SubPath: "cert.pem",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Contains(t, ars.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
|
||||||
|
Name: "NODE_EXTRA_CA_CERTS",
|
||||||
|
Value: "/runner/mount/path/cert.pem",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Contains(t, ars.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
|
||||||
|
Name: "RUNNER_UPDATE_CA_CERTS",
|
||||||
|
Value: "1",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("mode: dind", func(t *testing.T) {
|
||||||
|
options := &helm.Options{
|
||||||
|
SetValues: map[string]string{
|
||||||
|
"githubConfigUrl": "https://github.com/actions",
|
||||||
|
"githubConfigSecret": "pre-defined-secrets",
|
||||||
|
"githubServerTLS.certificateFrom.configMapKeyRef.name": "certs-configmap",
|
||||||
|
"githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem",
|
||||||
|
"githubServerTLS.runnerMountPath": "/runner/mount/path/",
|
||||||
|
"containerMode.type": "dind",
|
||||||
|
},
|
||||||
|
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
|
||||||
|
}
|
||||||
|
|
||||||
|
ars := render(t, options)
|
||||||
|
|
||||||
|
require.NotNil(t, ars.Spec.GitHubServerTLS)
|
||||||
|
expected := &v1alpha1.GitHubServerTLSConfig{
|
||||||
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
Name: "certs-configmap",
|
||||||
|
},
|
||||||
|
Key: "cert.pem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, expected, ars.Spec.GitHubServerTLS)
|
||||||
|
|
||||||
|
var volume *corev1.Volume
|
||||||
|
for _, v := range ars.Spec.Template.Spec.Volumes {
|
||||||
|
if v.Name == "github-server-tls-cert" {
|
||||||
|
volume = &v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.NotNil(t, volume)
|
||||||
|
assert.Equal(t, "certs-configmap", volume.ConfigMap.LocalObjectReference.Name)
|
||||||
|
assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Key)
|
||||||
|
assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Path)
|
||||||
|
|
||||||
|
assert.Contains(t, ars.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
|
||||||
|
Name: "github-server-tls-cert",
|
||||||
|
MountPath: "/runner/mount/path/cert.pem",
|
||||||
|
SubPath: "cert.pem",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Contains(t, ars.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
|
||||||
|
Name: "NODE_EXTRA_CA_CERTS",
|
||||||
|
Value: "/runner/mount/path/cert.pem",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Contains(t, ars.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
|
||||||
|
Name: "RUNNER_UPDATE_CA_CERTS",
|
||||||
|
Value: "1",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("mode: kubernetes", func(t *testing.T) {
|
||||||
|
options := &helm.Options{
|
||||||
|
SetValues: map[string]string{
|
||||||
|
"githubConfigUrl": "https://github.com/actions",
|
||||||
|
"githubConfigSecret": "pre-defined-secrets",
|
||||||
|
"githubServerTLS.certificateFrom.configMapKeyRef.name": "certs-configmap",
|
||||||
|
"githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem",
|
||||||
|
"githubServerTLS.runnerMountPath": "/runner/mount/path",
|
||||||
|
"containerMode.type": "kubernetes",
|
||||||
|
},
|
||||||
|
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
|
||||||
|
}
|
||||||
|
|
||||||
|
ars := render(t, options)
|
||||||
|
|
||||||
|
require.NotNil(t, ars.Spec.GitHubServerTLS)
|
||||||
|
expected := &v1alpha1.GitHubServerTLSConfig{
|
||||||
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
Name: "certs-configmap",
|
||||||
|
},
|
||||||
|
Key: "cert.pem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, expected, ars.Spec.GitHubServerTLS)
|
||||||
|
|
||||||
|
var volume *corev1.Volume
|
||||||
|
for _, v := range ars.Spec.Template.Spec.Volumes {
|
||||||
|
if v.Name == "github-server-tls-cert" {
|
||||||
|
volume = &v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.NotNil(t, volume)
|
||||||
|
assert.Equal(t, "certs-configmap", volume.ConfigMap.LocalObjectReference.Name)
|
||||||
|
assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Key)
|
||||||
|
assert.Equal(t, "cert.pem", volume.ConfigMap.Items[0].Path)
|
||||||
|
|
||||||
|
assert.Contains(t, ars.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
|
||||||
|
Name: "github-server-tls-cert",
|
||||||
|
MountPath: "/runner/mount/path/cert.pem",
|
||||||
|
SubPath: "cert.pem",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Contains(t, ars.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
|
||||||
|
Name: "NODE_EXTRA_CA_CERTS",
|
||||||
|
Value: "/runner/mount/path/cert.pem",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Contains(t, ars.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
|
||||||
|
Name: "RUNNER_UPDATE_CA_CERTS",
|
||||||
|
Value: "1",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("without providing githubServerTLS.runnerMountPath", func(t *testing.T) {
|
||||||
|
t.Run("mode: default", func(t *testing.T) {
|
||||||
|
options := &helm.Options{
|
||||||
|
SetValues: map[string]string{
|
||||||
|
"githubConfigUrl": "https://github.com/actions",
|
||||||
|
"githubConfigSecret": "pre-defined-secrets",
|
||||||
|
"githubServerTLS.certificateFrom.configMapKeyRef.name": "certs-configmap",
|
||||||
|
"githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem",
|
||||||
|
},
|
||||||
|
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
|
||||||
|
}
|
||||||
|
|
||||||
|
ars := render(t, options)
|
||||||
|
|
||||||
|
require.NotNil(t, ars.Spec.GitHubServerTLS)
|
||||||
|
expected := &v1alpha1.GitHubServerTLSConfig{
|
||||||
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
Name: "certs-configmap",
|
||||||
|
},
|
||||||
|
Key: "cert.pem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, expected, ars.Spec.GitHubServerTLS)
|
||||||
|
|
||||||
|
var volume *corev1.Volume
|
||||||
|
for _, v := range ars.Spec.Template.Spec.Volumes {
|
||||||
|
if v.Name == "github-server-tls-cert" {
|
||||||
|
volume = &v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.Nil(t, volume)
|
||||||
|
|
||||||
|
assert.NotContains(t, ars.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
|
||||||
|
Name: "github-server-tls-cert",
|
||||||
|
MountPath: "/runner/mount/path/cert.pem",
|
||||||
|
SubPath: "cert.pem",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NotContains(t, ars.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
|
||||||
|
Name: "NODE_EXTRA_CA_CERTS",
|
||||||
|
Value: "/runner/mount/path/cert.pem",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NotContains(t, ars.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
|
||||||
|
Name: "RUNNER_UPDATE_CA_CERTS",
|
||||||
|
Value: "1",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("mode: dind", func(t *testing.T) {
|
||||||
|
options := &helm.Options{
|
||||||
|
SetValues: map[string]string{
|
||||||
|
"githubConfigUrl": "https://github.com/actions",
|
||||||
|
"githubConfigSecret": "pre-defined-secrets",
|
||||||
|
"githubServerTLS.certificateFrom.configMapKeyRef.name": "certs-configmap",
|
||||||
|
"githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem",
|
||||||
|
"containerMode.type": "dind",
|
||||||
|
},
|
||||||
|
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
|
||||||
|
}
|
||||||
|
|
||||||
|
ars := render(t, options)
|
||||||
|
|
||||||
|
require.NotNil(t, ars.Spec.GitHubServerTLS)
|
||||||
|
expected := &v1alpha1.GitHubServerTLSConfig{
|
||||||
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
Name: "certs-configmap",
|
||||||
|
},
|
||||||
|
Key: "cert.pem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, expected, ars.Spec.GitHubServerTLS)
|
||||||
|
|
||||||
|
var volume *corev1.Volume
|
||||||
|
for _, v := range ars.Spec.Template.Spec.Volumes {
|
||||||
|
if v.Name == "github-server-tls-cert" {
|
||||||
|
volume = &v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.Nil(t, volume)
|
||||||
|
|
||||||
|
assert.NotContains(t, ars.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
|
||||||
|
Name: "github-server-tls-cert",
|
||||||
|
MountPath: "/runner/mount/path/cert.pem",
|
||||||
|
SubPath: "cert.pem",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NotContains(t, ars.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
|
||||||
|
Name: "NODE_EXTRA_CA_CERTS",
|
||||||
|
Value: "/runner/mount/path/cert.pem",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NotContains(t, ars.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
|
||||||
|
Name: "RUNNER_UPDATE_CA_CERTS",
|
||||||
|
Value: "1",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("mode: kubernetes", func(t *testing.T) {
|
||||||
|
options := &helm.Options{
|
||||||
|
SetValues: map[string]string{
|
||||||
|
"githubConfigUrl": "https://github.com/actions",
|
||||||
|
"githubConfigSecret": "pre-defined-secrets",
|
||||||
|
"githubServerTLS.certificateFrom.configMapKeyRef.name": "certs-configmap",
|
||||||
|
"githubServerTLS.certificateFrom.configMapKeyRef.key": "cert.pem",
|
||||||
|
"containerMode.type": "kubernetes",
|
||||||
|
},
|
||||||
|
KubectlOptions: k8s.NewKubectlOptions("", "", namespaceName),
|
||||||
|
}
|
||||||
|
|
||||||
|
ars := render(t, options)
|
||||||
|
|
||||||
|
require.NotNil(t, ars.Spec.GitHubServerTLS)
|
||||||
|
expected := &v1alpha1.GitHubServerTLSConfig{
|
||||||
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
Name: "certs-configmap",
|
||||||
|
},
|
||||||
|
Key: "cert.pem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, expected, ars.Spec.GitHubServerTLS)
|
||||||
|
|
||||||
|
var volume *corev1.Volume
|
||||||
|
for _, v := range ars.Spec.Template.Spec.Volumes {
|
||||||
|
if v.Name == "github-server-tls-cert" {
|
||||||
|
volume = &v
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.Nil(t, volume)
|
||||||
|
|
||||||
|
assert.NotContains(t, ars.Spec.Template.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{
|
||||||
|
Name: "github-server-tls-cert",
|
||||||
|
MountPath: "/runner/mount/path/cert.pem",
|
||||||
|
SubPath: "cert.pem",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NotContains(t, ars.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
|
||||||
|
Name: "NODE_EXTRA_CA_CERTS",
|
||||||
|
Value: "/runner/mount/path/cert.pem",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NotContains(t, ars.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
|
||||||
|
Name: "RUNNER_UPDATE_CA_CERTS",
|
||||||
|
Value: "1",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestTemplateNamingConstraints(t *testing.T) {
|
func TestTemplateNamingConstraints(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ githubConfigUrl: ""
|
||||||
|
|
||||||
## githubConfigSecret is the k8s secrets to use when auth with GitHub API.
|
## githubConfigSecret is the k8s secrets to use when auth with GitHub API.
|
||||||
## You can choose to use GitHub App or a PAT token
|
## You can choose to use GitHub App or a PAT token
|
||||||
githubConfigSecret:
|
githubConfigSecret:
|
||||||
### GitHub Apps Configuration
|
### GitHub Apps Configuration
|
||||||
## NOTE: IDs MUST be strings, use quotes
|
## NOTE: IDs MUST be strings, use quotes
|
||||||
#github_app_id: ""
|
#github_app_id: ""
|
||||||
|
|
@ -47,6 +47,27 @@ githubConfigSecret:
|
||||||
## name of the runner scale set to create. Defaults to the helm release name
|
## name of the runner scale set to create. Defaults to the helm release name
|
||||||
# runnerScaleSetName: ""
|
# runnerScaleSetName: ""
|
||||||
|
|
||||||
|
## A self-signed CA certificate for communication with the GitHub server can be
|
||||||
|
## provided using a config map key selector. If `runnerMountPath` is set, for
|
||||||
|
## each runner pod ARC will:
|
||||||
|
## - create a `github-server-tls-cert` volume containing the certificate
|
||||||
|
## specified in `certificateFrom`
|
||||||
|
## - mount that volume on path `runnerMountPath`/{certificate name}
|
||||||
|
## - set NODE_EXTRA_CA_CERTS environment variable to that same path
|
||||||
|
## - set RUNNER_UPDATE_CA_CERTS environment variable to "1" (as of version
|
||||||
|
## 2.303.0 this will instruct the runner to reload certificates on the host)
|
||||||
|
##
|
||||||
|
## If any of the above had already been set by the user in the runner pod
|
||||||
|
## template, ARC will observe those and not overwrite them.
|
||||||
|
## Example configuration:
|
||||||
|
#
|
||||||
|
# githubServerTLS:
|
||||||
|
# certificateFrom:
|
||||||
|
# configMapKeyRef:
|
||||||
|
# name: config-map-name
|
||||||
|
# key: ca.pem
|
||||||
|
# runnerMountPath: /usr/local/share/ca-certificates/
|
||||||
|
|
||||||
## template is the PodSpec for each runner Pod
|
## template is the PodSpec for each runner Pod
|
||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
|
|
@ -139,4 +160,4 @@ containerMode:
|
||||||
storageClassName: "dynamic-blob-storage"
|
storageClassName: "dynamic-blob-storage"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
storage: 1Gi
|
storage: 1Gi
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
@ -44,6 +45,7 @@ type RunnerScaleSetListenerConfig struct {
|
||||||
MaxRunners int `split_words:"true"`
|
MaxRunners int `split_words:"true"`
|
||||||
MinRunners int `split_words:"true"`
|
MinRunners int `split_words:"true"`
|
||||||
RunnerScaleSetId int `split_words:"true"`
|
RunnerScaleSetId int `split_words:"true"`
|
||||||
|
ServerRootCA string `split_words:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -90,8 +92,8 @@ func run(rc RunnerScaleSetListenerConfig, logger logr.Logger) error {
|
||||||
actionsServiceClient, err := newActionsClientFromConfig(
|
actionsServiceClient, err := newActionsClientFromConfig(
|
||||||
rc,
|
rc,
|
||||||
creds,
|
creds,
|
||||||
actions.WithUserAgent(fmt.Sprintf("actions-runner-controller/%s", build.Version)),
|
|
||||||
actions.WithLogger(logger),
|
actions.WithLogger(logger),
|
||||||
|
actions.WithUserAgent(fmt.Sprintf("actions-runner-controller/%s", build.Version)),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create an Actions Service client: %w", err)
|
return fmt.Errorf("failed to create an Actions Service client: %w", err)
|
||||||
|
|
@ -160,6 +162,20 @@ func validateConfig(config *RunnerScaleSetListenerConfig) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newActionsClientFromConfig(config RunnerScaleSetListenerConfig, creds *actions.ActionsAuth, options ...actions.ClientOption) (*actions.Client, error) {
|
func newActionsClientFromConfig(config RunnerScaleSetListenerConfig, creds *actions.ActionsAuth, options ...actions.ClientOption) (*actions.Client, error) {
|
||||||
|
if config.ServerRootCA != "" {
|
||||||
|
systemPool, err := x509.SystemCertPool()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load system cert pool: %w", err)
|
||||||
|
}
|
||||||
|
pool := systemPool.Clone()
|
||||||
|
ok := pool.AppendCertsFromPEM([]byte(config.ServerRootCA))
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to parse root certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
options = append(options, actions.WithRootCAs(pool))
|
||||||
|
}
|
||||||
|
|
||||||
proxyFunc := httpproxy.FromEnvironment().ProxyFunc()
|
proxyFunc := httpproxy.FromEnvironment().ProxyFunc()
|
||||||
options = append(options, actions.WithProxy(func(req *http.Request) (*url.URL, error) {
|
options = append(options, actions.WithProxy(func(req *http.Request) (*url.URL, error) {
|
||||||
return proxyFunc(req.URL)
|
return proxyFunc(req.URL)
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,20 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
"github.com/actions/actions-runner-controller/github/actions"
|
||||||
|
"github.com/actions/actions-runner-controller/github/actions/testserver"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConfigValidationMinMax(t *testing.T) {
|
func TestConfigValidationMinMax(t *testing.T) {
|
||||||
|
|
@ -97,6 +101,54 @@ func TestConfigValidationConfigUrl(t *testing.T) {
|
||||||
assert.ErrorContains(t, err, "GitHubConfigUrl is not provided", "Expected error about missing ConfigureUrl")
|
assert.ErrorContains(t, err, "GitHubConfigUrl is not provided", "Expected error about missing ConfigureUrl")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCustomerServerRootCA(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
certsFolder := filepath.Join(
|
||||||
|
"../../",
|
||||||
|
"github",
|
||||||
|
"actions",
|
||||||
|
"testdata",
|
||||||
|
)
|
||||||
|
certPath := filepath.Join(certsFolder, "server.crt")
|
||||||
|
keyPath := filepath.Join(certsFolder, "server.key")
|
||||||
|
|
||||||
|
serverCalledSuccessfully := false
|
||||||
|
|
||||||
|
server := testserver.NewUnstarted(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
serverCalledSuccessfully = true
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(`{"count": 0}`))
|
||||||
|
}))
|
||||||
|
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||||
|
server.StartTLS()
|
||||||
|
|
||||||
|
var certsString string
|
||||||
|
rootCA, err := os.ReadFile(filepath.Join(certsFolder, "rootCA.crt"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
certsString = string(rootCA)
|
||||||
|
|
||||||
|
intermediate, err := os.ReadFile(filepath.Join(certsFolder, "intermediate.pem"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
certsString = certsString + string(intermediate)
|
||||||
|
|
||||||
|
config := RunnerScaleSetListenerConfig{
|
||||||
|
ConfigureUrl: server.ConfigURLForOrg("myorg"),
|
||||||
|
ServerRootCA: certsString,
|
||||||
|
}
|
||||||
|
creds := &actions.ActionsAuth{
|
||||||
|
Token: "token",
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := newActionsClientFromConfig(config, creds)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = client.GetRunnerScaleSet(ctx, "test")
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, serverCalledSuccessfully)
|
||||||
|
}
|
||||||
|
|
||||||
func TestProxySettings(t *testing.T) {
|
func TestProxySettings(t *testing.T) {
|
||||||
t.Run("http", func(t *testing.T) {
|
t.Run("http", func(t *testing.T) {
|
||||||
wentThroughProxy := false
|
wentThroughProxy := false
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,28 @@ spec:
|
||||||
githubConfigUrl:
|
githubConfigUrl:
|
||||||
description: Required
|
description: Required
|
||||||
type: string
|
type: string
|
||||||
|
githubServerTLS:
|
||||||
|
properties:
|
||||||
|
certificateFrom:
|
||||||
|
description: Required
|
||||||
|
properties:
|
||||||
|
configMapKeyRef:
|
||||||
|
description: Required
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
description: The key to select.
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||||
|
type: string
|
||||||
|
optional:
|
||||||
|
description: Specify whether the ConfigMap or its key must be defined
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
image:
|
image:
|
||||||
description: Required
|
description: Required
|
||||||
type: string
|
type: string
|
||||||
|
|
|
||||||
|
|
@ -51,9 +51,25 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
githubServerTLS:
|
githubServerTLS:
|
||||||
properties:
|
properties:
|
||||||
certConfigMapRef:
|
certificateFrom:
|
||||||
description: Required
|
description: Required
|
||||||
type: string
|
properties:
|
||||||
|
configMapKeyRef:
|
||||||
|
description: Required
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
description: The key to select.
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||||
|
type: string
|
||||||
|
optional:
|
||||||
|
description: Specify whether the ConfigMap or its key must be defined
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
type: object
|
type: object
|
||||||
maxRunners:
|
maxRunners:
|
||||||
minimum: 0
|
minimum: 0
|
||||||
|
|
|
||||||
|
|
@ -64,9 +64,25 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
githubServerTLS:
|
githubServerTLS:
|
||||||
properties:
|
properties:
|
||||||
certConfigMapRef:
|
certificateFrom:
|
||||||
description: Required
|
description: Required
|
||||||
type: string
|
properties:
|
||||||
|
configMapKeyRef:
|
||||||
|
description: Required
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
description: The key to select.
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||||
|
type: string
|
||||||
|
optional:
|
||||||
|
description: Specify whether the ConfigMap or its key must be defined
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
type: object
|
type: object
|
||||||
metadata:
|
metadata:
|
||||||
description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata'
|
description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata'
|
||||||
|
|
|
||||||
|
|
@ -46,9 +46,25 @@ spec:
|
||||||
type: string
|
type: string
|
||||||
githubServerTLS:
|
githubServerTLS:
|
||||||
properties:
|
properties:
|
||||||
certConfigMapRef:
|
certificateFrom:
|
||||||
description: Required
|
description: Required
|
||||||
type: string
|
properties:
|
||||||
|
configMapKeyRef:
|
||||||
|
description: Required
|
||||||
|
properties:
|
||||||
|
key:
|
||||||
|
description: The key to select.
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||||
|
type: string
|
||||||
|
optional:
|
||||||
|
description: Specify whether the ConfigMap or its key must be defined
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- key
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
type: object
|
type: object
|
||||||
metadata:
|
metadata:
|
||||||
description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata'
|
description: 'Standard object''s metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata'
|
||||||
|
|
|
||||||
|
|
@ -423,6 +423,15 @@ func (r *AutoscalingListenerReconciler) createListenerPod(ctx context.Context, a
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if autoscalingListener.Spec.GitHubServerTLS != nil {
|
||||||
|
env, err := r.certificateEnvVarForListener(ctx, autoscalingRunnerSet, autoscalingListener)
|
||||||
|
if err != nil {
|
||||||
|
return ctrl.Result{}, fmt.Errorf("failed to create certificate env var for listener: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
envs = append(envs, env)
|
||||||
|
}
|
||||||
|
|
||||||
newPod := r.resourceBuilder.newScaleSetListenerPod(autoscalingListener, serviceAccount, secret, envs...)
|
newPod := r.resourceBuilder.newScaleSetListenerPod(autoscalingListener, serviceAccount, secret, envs...)
|
||||||
|
|
||||||
if err := ctrl.SetControllerReference(autoscalingListener, newPod, r.Scheme); err != nil {
|
if err := ctrl.SetControllerReference(autoscalingListener, newPod, r.Scheme); err != nil {
|
||||||
|
|
@ -439,6 +448,47 @@ func (r *AutoscalingListenerReconciler) createListenerPod(ctx context.Context, a
|
||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *AutoscalingListenerReconciler) certificateEnvVarForListener(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet, autoscalingListener *v1alpha1.AutoscalingListener) (corev1.EnvVar, error) {
|
||||||
|
if autoscalingListener.Spec.GitHubServerTLS.CertificateFrom == nil {
|
||||||
|
return corev1.EnvVar{}, fmt.Errorf("githubServerTLS.certificateFrom is not specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
if autoscalingListener.Spec.GitHubServerTLS.CertificateFrom.ConfigMapKeyRef == nil {
|
||||||
|
return corev1.EnvVar{}, fmt.Errorf("githubServerTLS.certificateFrom.configMapKeyRef is not specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
var configmap corev1.ConfigMap
|
||||||
|
err := r.Get(
|
||||||
|
ctx,
|
||||||
|
types.NamespacedName{
|
||||||
|
Namespace: autoscalingRunnerSet.Namespace,
|
||||||
|
Name: autoscalingListener.Spec.GitHubServerTLS.CertificateFrom.ConfigMapKeyRef.Name,
|
||||||
|
},
|
||||||
|
&configmap,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return corev1.EnvVar{}, fmt.Errorf(
|
||||||
|
"failed to get configmap %s: %w",
|
||||||
|
autoscalingListener.Spec.GitHubServerTLS.CertificateFrom.ConfigMapKeyRef.Name,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
certificate, ok := configmap.Data[autoscalingListener.Spec.GitHubServerTLS.CertificateFrom.ConfigMapKeyRef.Key]
|
||||||
|
if !ok {
|
||||||
|
return corev1.EnvVar{}, fmt.Errorf(
|
||||||
|
"key %s is not found in configmap %s",
|
||||||
|
autoscalingListener.Spec.GitHubServerTLS.CertificateFrom.ConfigMapKeyRef.Key,
|
||||||
|
autoscalingListener.Spec.GitHubServerTLS.CertificateFrom.ConfigMapKeyRef.Name,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return corev1.EnvVar{
|
||||||
|
Name: "GITHUB_SERVER_ROOT_CA",
|
||||||
|
Value: certificate,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *AutoscalingListenerReconciler) createSecretsForListener(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, secret *corev1.Secret, logger logr.Logger) (ctrl.Result, error) {
|
func (r *AutoscalingListenerReconciler) createSecretsForListener(ctx context.Context, autoscalingListener *v1alpha1.AutoscalingListener, secret *corev1.Secret, logger logr.Logger) (ctrl.Result, error) {
|
||||||
newListenerSecret := r.resourceBuilder.newScaleSetListenerSecretMirror(autoscalingListener, secret)
|
newListenerSecret := r.resourceBuilder.newScaleSetListenerSecretMirror(autoscalingListener, secret)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ package actionsgithubcom
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
|
@ -554,3 +556,165 @@ var _ = Describe("Test AutoScalingListener controller with proxy", func() {
|
||||||
autoscalingListenerTestInterval).Should(Succeed(), "failed to delete secret with proxy details")
|
autoscalingListenerTestInterval).Should(Succeed(), "failed to delete secret with proxy details")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var _ = Describe("Test GitHub Server TLS configuration", func() {
|
||||||
|
var ctx context.Context
|
||||||
|
var mgr ctrl.Manager
|
||||||
|
var autoscalingNS *corev1.Namespace
|
||||||
|
var autoscalingRunnerSet *actionsv1alpha1.AutoscalingRunnerSet
|
||||||
|
var configSecret *corev1.Secret
|
||||||
|
var autoscalingListener *actionsv1alpha1.AutoscalingListener
|
||||||
|
var rootCAConfigMap *corev1.ConfigMap
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
ctx = context.Background()
|
||||||
|
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
|
||||||
|
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
|
||||||
|
|
||||||
|
cert, err := os.ReadFile(filepath.Join(
|
||||||
|
"../../",
|
||||||
|
"github",
|
||||||
|
"actions",
|
||||||
|
"testdata",
|
||||||
|
"rootCA.crt",
|
||||||
|
))
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to read root CA cert")
|
||||||
|
rootCAConfigMap = &corev1.ConfigMap{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "root-ca-configmap",
|
||||||
|
Namespace: autoscalingNS.Name,
|
||||||
|
},
|
||||||
|
Data: map[string]string{
|
||||||
|
"rootCA.crt": string(cert),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = k8sClient.Create(ctx, rootCAConfigMap)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to create configmap with root CAs")
|
||||||
|
|
||||||
|
controller := &AutoscalingListenerReconciler{
|
||||||
|
Client: mgr.GetClient(),
|
||||||
|
Scheme: mgr.GetScheme(),
|
||||||
|
Log: logf.Log,
|
||||||
|
}
|
||||||
|
err = controller.SetupWithManager(mgr)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
||||||
|
|
||||||
|
min := 1
|
||||||
|
max := 10
|
||||||
|
autoscalingRunnerSet = &actionsv1alpha1.AutoscalingRunnerSet{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-asrs",
|
||||||
|
Namespace: autoscalingNS.Name,
|
||||||
|
},
|
||||||
|
Spec: actionsv1alpha1.AutoscalingRunnerSetSpec{
|
||||||
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
||||||
|
GitHubConfigSecret: configSecret.Name,
|
||||||
|
GitHubServerTLS: &actionsv1alpha1.GitHubServerTLSConfig{
|
||||||
|
CertificateFrom: &actionsv1alpha1.TLSCertificateSource{
|
||||||
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
Name: rootCAConfigMap.Name,
|
||||||
|
},
|
||||||
|
Key: "rootCA.crt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MaxRunners: &max,
|
||||||
|
MinRunners: &min,
|
||||||
|
Template: corev1.PodTemplateSpec{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "runner",
|
||||||
|
Image: "ghcr.io/actions/runner",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = k8sClient.Create(ctx, autoscalingRunnerSet)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
||||||
|
|
||||||
|
autoscalingListener = &actionsv1alpha1.AutoscalingListener{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-asl",
|
||||||
|
Namespace: autoscalingNS.Name,
|
||||||
|
},
|
||||||
|
Spec: actionsv1alpha1.AutoscalingListenerSpec{
|
||||||
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
||||||
|
GitHubConfigSecret: configSecret.Name,
|
||||||
|
GitHubServerTLS: &actionsv1alpha1.GitHubServerTLSConfig{
|
||||||
|
CertificateFrom: &actionsv1alpha1.TLSCertificateSource{
|
||||||
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
Name: rootCAConfigMap.Name,
|
||||||
|
},
|
||||||
|
Key: "rootCA.crt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RunnerScaleSetId: 1,
|
||||||
|
AutoscalingRunnerSetNamespace: autoscalingRunnerSet.Namespace,
|
||||||
|
AutoscalingRunnerSetName: autoscalingRunnerSet.Name,
|
||||||
|
EphemeralRunnerSetName: "test-ers",
|
||||||
|
MaxRunners: 10,
|
||||||
|
MinRunners: 1,
|
||||||
|
Image: "ghcr.io/owner/repo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = k8sClient.Create(ctx, autoscalingListener)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingListener")
|
||||||
|
|
||||||
|
startManagers(GinkgoT(), mgr)
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("When creating a new AutoScalingListener", func() {
|
||||||
|
It("It should set the certificates as an environment variable on the pod", func() {
|
||||||
|
pod := new(corev1.Pod)
|
||||||
|
Eventually(
|
||||||
|
func(g Gomega) {
|
||||||
|
err := k8sClient.Get(
|
||||||
|
ctx,
|
||||||
|
client.ObjectKey{
|
||||||
|
Name: autoscalingListener.Name,
|
||||||
|
Namespace: autoscalingListener.Namespace,
|
||||||
|
},
|
||||||
|
pod,
|
||||||
|
)
|
||||||
|
|
||||||
|
g.Expect(err).NotTo(HaveOccurred(), "failed to get pod")
|
||||||
|
g.Expect(pod.Spec.Containers).NotTo(BeEmpty(), "pod should have containers")
|
||||||
|
g.Expect(pod.Spec.Containers[0].Env).NotTo(BeEmpty(), "pod should have env variables")
|
||||||
|
|
||||||
|
var env *corev1.EnvVar
|
||||||
|
for _, e := range pod.Spec.Containers[0].Env {
|
||||||
|
if e.Name == "GITHUB_SERVER_ROOT_CA" {
|
||||||
|
env = &e
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.Expect(env).NotTo(BeNil(), "pod should have an env variable named GITHUB_SERVER_ROOT_CA_PATH")
|
||||||
|
|
||||||
|
cert, err := os.ReadFile(filepath.Join(
|
||||||
|
"../../",
|
||||||
|
"github",
|
||||||
|
"actions",
|
||||||
|
"testdata",
|
||||||
|
"rootCA.crt",
|
||||||
|
))
|
||||||
|
g.Expect(err).NotTo(HaveOccurred(), "failed to read rootCA.crt")
|
||||||
|
|
||||||
|
g.Expect(env.Value).To(
|
||||||
|
BeEquivalentTo(string(cert)),
|
||||||
|
"GITHUB_SERVER_ROOT_CA should be the rootCA.crt",
|
||||||
|
)
|
||||||
|
}).
|
||||||
|
WithTimeout(autoscalingRunnerSetTestTimeout).
|
||||||
|
WithPolling(autoscalingListenerTestInterval).
|
||||||
|
Should(Succeed(), "failed to create pod with volume and env variable")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -541,7 +541,23 @@ func (r *AutoscalingRunnerSetReconciler) actionsClientFor(ctx context.Context, a
|
||||||
return nil, fmt.Errorf("failed to find GitHub config secret: %w", err)
|
return nil, fmt.Errorf("failed to find GitHub config secret: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var opts []actions.ClientOption
|
opts, err := r.actionsClientOptionsFor(ctx, autoscalingRunnerSet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get actions client options: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.ActionsClient.GetClientFromSecret(
|
||||||
|
ctx,
|
||||||
|
autoscalingRunnerSet.Spec.GitHubConfigUrl,
|
||||||
|
autoscalingRunnerSet.Namespace,
|
||||||
|
configSecret.Data,
|
||||||
|
opts...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AutoscalingRunnerSetReconciler) actionsClientOptionsFor(ctx context.Context, autoscalingRunnerSet *v1alpha1.AutoscalingRunnerSet) ([]actions.ClientOption, error) {
|
||||||
|
var options []actions.ClientOption
|
||||||
|
|
||||||
if autoscalingRunnerSet.Spec.Proxy != nil {
|
if autoscalingRunnerSet.Spec.Proxy != nil {
|
||||||
proxyFunc, err := autoscalingRunnerSet.Spec.Proxy.ProxyFunc(func(s string) (*corev1.Secret, error) {
|
proxyFunc, err := autoscalingRunnerSet.Spec.Proxy.ProxyFunc(func(s string) (*corev1.Secret, error) {
|
||||||
var secret corev1.Secret
|
var secret corev1.Secret
|
||||||
|
|
@ -556,16 +572,35 @@ func (r *AutoscalingRunnerSetReconciler) actionsClientFor(ctx context.Context, a
|
||||||
return nil, fmt.Errorf("failed to get proxy func: %w", err)
|
return nil, fmt.Errorf("failed to get proxy func: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts = append(opts, actions.WithProxy(proxyFunc))
|
options = append(options, actions.WithProxy(proxyFunc))
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.ActionsClient.GetClientFromSecret(
|
tlsConfig := autoscalingRunnerSet.Spec.GitHubServerTLS
|
||||||
ctx,
|
if tlsConfig != nil {
|
||||||
autoscalingRunnerSet.Spec.GitHubConfigUrl,
|
pool, err := tlsConfig.ToCertPool(func(name, key string) ([]byte, error) {
|
||||||
autoscalingRunnerSet.Namespace,
|
var configmap corev1.ConfigMap
|
||||||
configSecret.Data,
|
err := r.Get(
|
||||||
opts...,
|
ctx,
|
||||||
)
|
types.NamespacedName{
|
||||||
|
Namespace: autoscalingRunnerSet.Namespace,
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
&configmap,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get configmap %s: %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(configmap.Data[key]), nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get tls config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
options = append(options, actions.WithRootCAs(pool))
|
||||||
|
}
|
||||||
|
|
||||||
|
return options, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupWithManager sets up the controller with the Manager.
|
// SetupWithManager sets up the controller with the Manager.
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,13 @@ package actionsgithubcom
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -787,4 +790,242 @@ var _ = Describe("Test Client optional configuration", func() {
|
||||||
).Should(BeTrue(), "server was not called")
|
).Should(BeTrue(), "server was not called")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Context("When specifying a configmap for root CAs", func() {
|
||||||
|
var ctx context.Context
|
||||||
|
var mgr ctrl.Manager
|
||||||
|
var autoscalingNS *corev1.Namespace
|
||||||
|
var configSecret *corev1.Secret
|
||||||
|
var rootCAConfigMap *corev1.ConfigMap
|
||||||
|
var controller *AutoscalingRunnerSetReconciler
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
ctx = context.Background()
|
||||||
|
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
|
||||||
|
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
|
||||||
|
|
||||||
|
cert, err := os.ReadFile(filepath.Join(
|
||||||
|
"../../",
|
||||||
|
"github",
|
||||||
|
"actions",
|
||||||
|
"testdata",
|
||||||
|
"rootCA.crt",
|
||||||
|
))
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to read root CA cert")
|
||||||
|
rootCAConfigMap = &corev1.ConfigMap{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "root-ca-configmap",
|
||||||
|
Namespace: autoscalingNS.Name,
|
||||||
|
},
|
||||||
|
Data: map[string]string{
|
||||||
|
"rootCA.crt": string(cert),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = k8sClient.Create(ctx, rootCAConfigMap)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to create configmap with root CAs")
|
||||||
|
|
||||||
|
controller = &AutoscalingRunnerSetReconciler{
|
||||||
|
Client: mgr.GetClient(),
|
||||||
|
Scheme: mgr.GetScheme(),
|
||||||
|
Log: logf.Log,
|
||||||
|
ControllerNamespace: autoscalingNS.Name,
|
||||||
|
DefaultRunnerScaleSetListenerImage: "ghcr.io/actions/arc",
|
||||||
|
ActionsClient: fake.NewMultiClient(),
|
||||||
|
}
|
||||||
|
err = controller.SetupWithManager(mgr)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
||||||
|
|
||||||
|
startManagers(GinkgoT(), mgr)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should be able to make requests to a server using root CAs", func() {
|
||||||
|
controller.ActionsClient = actions.NewMultiClient("test", logr.Discard())
|
||||||
|
|
||||||
|
certsFolder := filepath.Join(
|
||||||
|
"../../",
|
||||||
|
"github",
|
||||||
|
"actions",
|
||||||
|
"testdata",
|
||||||
|
)
|
||||||
|
certPath := filepath.Join(certsFolder, "server.crt")
|
||||||
|
keyPath := filepath.Join(certsFolder, "server.key")
|
||||||
|
|
||||||
|
serverSuccessfullyCalled := false
|
||||||
|
server := testserver.NewUnstarted(GinkgoT(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
serverSuccessfullyCalled = true
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to load server cert")
|
||||||
|
|
||||||
|
server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||||
|
server.StartTLS()
|
||||||
|
|
||||||
|
min := 1
|
||||||
|
max := 10
|
||||||
|
autoscalingRunnerSet := &v1alpha1.AutoscalingRunnerSet{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-asrs",
|
||||||
|
Namespace: autoscalingNS.Name,
|
||||||
|
},
|
||||||
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
||||||
|
GitHubConfigUrl: server.ConfigURLForOrg("my-org"),
|
||||||
|
GitHubConfigSecret: configSecret.Name,
|
||||||
|
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
|
||||||
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
Name: rootCAConfigMap.Name,
|
||||||
|
},
|
||||||
|
Key: "rootCA.crt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MaxRunners: &max,
|
||||||
|
MinRunners: &min,
|
||||||
|
RunnerGroup: "testgroup",
|
||||||
|
Template: corev1.PodTemplateSpec{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "runner",
|
||||||
|
Image: "ghcr.io/actions/runner",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = k8sClient.Create(ctx, autoscalingRunnerSet)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
||||||
|
|
||||||
|
// wait for server to be called
|
||||||
|
Eventually(
|
||||||
|
func() (bool, error) {
|
||||||
|
return serverSuccessfullyCalled, nil
|
||||||
|
},
|
||||||
|
autoscalingRunnerSetTestTimeout,
|
||||||
|
1*time.Nanosecond,
|
||||||
|
).Should(BeTrue(), "server was not called")
|
||||||
|
})
|
||||||
|
|
||||||
|
It("it creates a listener referencing the right configmap for TLS", func() {
|
||||||
|
min := 1
|
||||||
|
max := 10
|
||||||
|
autoscalingRunnerSet := &v1alpha1.AutoscalingRunnerSet{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-asrs",
|
||||||
|
Namespace: autoscalingNS.Name,
|
||||||
|
},
|
||||||
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
||||||
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
||||||
|
GitHubConfigSecret: configSecret.Name,
|
||||||
|
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
|
||||||
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
Name: rootCAConfigMap.Name,
|
||||||
|
},
|
||||||
|
Key: "rootCA.crt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MaxRunners: &max,
|
||||||
|
MinRunners: &min,
|
||||||
|
RunnerGroup: "testgroup",
|
||||||
|
Template: corev1.PodTemplateSpec{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "runner",
|
||||||
|
Image: "ghcr.io/actions/runner",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := k8sClient.Create(ctx, autoscalingRunnerSet)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
||||||
|
|
||||||
|
Eventually(
|
||||||
|
func(g Gomega) {
|
||||||
|
listener := new(v1alpha1.AutoscalingListener)
|
||||||
|
err := k8sClient.Get(
|
||||||
|
ctx,
|
||||||
|
client.ObjectKey{
|
||||||
|
Name: scaleSetListenerName(autoscalingRunnerSet),
|
||||||
|
Namespace: autoscalingRunnerSet.Namespace,
|
||||||
|
},
|
||||||
|
listener,
|
||||||
|
)
|
||||||
|
g.Expect(err).NotTo(HaveOccurred(), "failed to get listener")
|
||||||
|
|
||||||
|
g.Expect(listener.Spec.GitHubServerTLS).NotTo(BeNil(), "listener does not have TLS config")
|
||||||
|
g.Expect(listener.Spec.GitHubServerTLS).To(BeEquivalentTo(autoscalingRunnerSet.Spec.GitHubServerTLS), "listener does not have TLS config")
|
||||||
|
},
|
||||||
|
autoscalingRunnerSetTestTimeout,
|
||||||
|
autoscalingListenerTestInterval,
|
||||||
|
).Should(Succeed(), "tls config is incorrect")
|
||||||
|
})
|
||||||
|
|
||||||
|
It("it creates an ephemeral runner set referencing the right configmap for TLS", func() {
|
||||||
|
min := 1
|
||||||
|
max := 10
|
||||||
|
autoscalingRunnerSet := &v1alpha1.AutoscalingRunnerSet{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-asrs",
|
||||||
|
Namespace: autoscalingNS.Name,
|
||||||
|
},
|
||||||
|
Spec: v1alpha1.AutoscalingRunnerSetSpec{
|
||||||
|
GitHubConfigUrl: "https://github.com/owner/repo",
|
||||||
|
GitHubConfigSecret: configSecret.Name,
|
||||||
|
GitHubServerTLS: &v1alpha1.GitHubServerTLSConfig{
|
||||||
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
Name: rootCAConfigMap.Name,
|
||||||
|
},
|
||||||
|
Key: "rootCA.crt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MaxRunners: &max,
|
||||||
|
MinRunners: &min,
|
||||||
|
RunnerGroup: "testgroup",
|
||||||
|
Template: corev1.PodTemplateSpec{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "runner",
|
||||||
|
Image: "ghcr.io/actions/runner",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := k8sClient.Create(ctx, autoscalingRunnerSet)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to create AutoScalingRunnerSet")
|
||||||
|
|
||||||
|
Eventually(
|
||||||
|
func(g Gomega) {
|
||||||
|
runnerSetList := new(v1alpha1.EphemeralRunnerSetList)
|
||||||
|
err := k8sClient.List(ctx, runnerSetList, client.InNamespace(autoscalingRunnerSet.Namespace))
|
||||||
|
g.Expect(err).NotTo(HaveOccurred(), "failed to list EphemeralRunnerSet")
|
||||||
|
g.Expect(runnerSetList.Items).To(HaveLen(1), "expected 1 EphemeralRunnerSet to be created")
|
||||||
|
|
||||||
|
runnerSet := &runnerSetList.Items[0]
|
||||||
|
|
||||||
|
g.Expect(runnerSet.Spec.EphemeralRunnerSpec.GitHubServerTLS).NotTo(BeNil(), "expected EphemeralRunnerSpec.GitHubServerTLS to be set")
|
||||||
|
g.Expect(runnerSet.Spec.EphemeralRunnerSpec.GitHubServerTLS).To(BeEquivalentTo(autoscalingRunnerSet.Spec.GitHubServerTLS), "EphemeralRunnerSpec does not have TLS config")
|
||||||
|
},
|
||||||
|
autoscalingRunnerSetTestTimeout,
|
||||||
|
autoscalingListenerTestInterval,
|
||||||
|
).Should(Succeed())
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -680,6 +680,21 @@ func (r *EphemeralRunnerReconciler) actionsClientFor(ctx context.Context, runner
|
||||||
return nil, fmt.Errorf("failed to get secret: %w", err)
|
return nil, fmt.Errorf("failed to get secret: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
opts, err := r.actionsClientOptionsFor(ctx, runner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get actions client options: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.ActionsClient.GetClientFromSecret(
|
||||||
|
ctx,
|
||||||
|
runner.Spec.GitHubConfigUrl,
|
||||||
|
runner.Namespace,
|
||||||
|
secret.Data,
|
||||||
|
opts...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EphemeralRunnerReconciler) actionsClientOptionsFor(ctx context.Context, runner *v1alpha1.EphemeralRunner) ([]actions.ClientOption, error) {
|
||||||
var opts []actions.ClientOption
|
var opts []actions.ClientOption
|
||||||
if runner.Spec.Proxy != nil {
|
if runner.Spec.Proxy != nil {
|
||||||
proxyFunc, err := runner.Spec.Proxy.ProxyFunc(func(s string) (*corev1.Secret, error) {
|
proxyFunc, err := runner.Spec.Proxy.ProxyFunc(func(s string) (*corev1.Secret, error) {
|
||||||
|
|
@ -698,13 +713,32 @@ func (r *EphemeralRunnerReconciler) actionsClientFor(ctx context.Context, runner
|
||||||
opts = append(opts, actions.WithProxy(proxyFunc))
|
opts = append(opts, actions.WithProxy(proxyFunc))
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.ActionsClient.GetClientFromSecret(
|
tlsConfig := runner.Spec.GitHubServerTLS
|
||||||
ctx,
|
if tlsConfig != nil {
|
||||||
runner.Spec.GitHubConfigUrl,
|
pool, err := tlsConfig.ToCertPool(func(name, key string) ([]byte, error) {
|
||||||
runner.Namespace,
|
var configmap corev1.ConfigMap
|
||||||
secret.Data,
|
err := r.Get(
|
||||||
opts...,
|
ctx,
|
||||||
)
|
types.NamespacedName{
|
||||||
|
Namespace: runner.Namespace,
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
&configmap,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get configmap %s: %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(configmap.Data[key]), nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get tls config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts = append(opts, actions.WithRootCAs(pool))
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// runnerRegisteredWithService checks if the runner is still registered with the service
|
// runnerRegisteredWithService checks if the runner is still registered with the service
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,13 @@ package actionsgithubcom
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -14,6 +17,7 @@ import (
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions/fake"
|
"github.com/actions/actions-runner-controller/github/actions/fake"
|
||||||
|
"github.com/actions/actions-runner-controller/github/actions/testserver"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
|
@ -841,4 +845,100 @@ var _ = Describe("EphemeralRunner", func() {
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Describe("TLS config", func() {
|
||||||
|
var ctx context.Context
|
||||||
|
var mgr ctrl.Manager
|
||||||
|
var autoScalingNS *corev1.Namespace
|
||||||
|
var configSecret *corev1.Secret
|
||||||
|
var controller *EphemeralRunnerReconciler
|
||||||
|
var rootCAConfigMap *corev1.ConfigMap
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
ctx = context.Background()
|
||||||
|
autoScalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
|
||||||
|
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoScalingNS.Name)
|
||||||
|
|
||||||
|
cert, err := os.ReadFile(filepath.Join(
|
||||||
|
"../../",
|
||||||
|
"github",
|
||||||
|
"actions",
|
||||||
|
"testdata",
|
||||||
|
"rootCA.crt",
|
||||||
|
))
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to read root CA cert")
|
||||||
|
rootCAConfigMap = &corev1.ConfigMap{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "root-ca-configmap",
|
||||||
|
Namespace: autoScalingNS.Name,
|
||||||
|
},
|
||||||
|
Data: map[string]string{
|
||||||
|
"rootCA.crt": string(cert),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = k8sClient.Create(ctx, rootCAConfigMap)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to create configmap with root CAs")
|
||||||
|
|
||||||
|
controller = &EphemeralRunnerReconciler{
|
||||||
|
Client: mgr.GetClient(),
|
||||||
|
Scheme: mgr.GetScheme(),
|
||||||
|
Log: logf.Log,
|
||||||
|
ActionsClient: fake.NewMultiClient(),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = controller.SetupWithManager(mgr)
|
||||||
|
Expect(err).To(BeNil(), "failed to setup controller")
|
||||||
|
|
||||||
|
startManagers(GinkgoT(), mgr)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should be able to make requests to a server using root CAs", func() {
|
||||||
|
certsFolder := filepath.Join(
|
||||||
|
"../../",
|
||||||
|
"github",
|
||||||
|
"actions",
|
||||||
|
"testdata",
|
||||||
|
)
|
||||||
|
certPath := filepath.Join(certsFolder, "server.crt")
|
||||||
|
keyPath := filepath.Join(certsFolder, "server.key")
|
||||||
|
|
||||||
|
serverSuccessfullyCalled := false
|
||||||
|
server := testserver.NewUnstarted(GinkgoT(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
serverSuccessfullyCalled = true
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to load server cert")
|
||||||
|
|
||||||
|
server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||||
|
server.StartTLS()
|
||||||
|
|
||||||
|
// Use an actual client
|
||||||
|
controller.ActionsClient = actions.NewMultiClient("test", logr.Discard())
|
||||||
|
|
||||||
|
ephemeralRunner := newExampleRunner("test-runner", autoScalingNS.Name, configSecret.Name)
|
||||||
|
ephemeralRunner.Spec.GitHubConfigUrl = server.ConfigURLForOrg("my-org")
|
||||||
|
ephemeralRunner.Spec.GitHubServerTLS = &v1alpha1.GitHubServerTLSConfig{
|
||||||
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
Name: rootCAConfigMap.Name,
|
||||||
|
},
|
||||||
|
Key: "rootCA.crt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = k8sClient.Create(ctx, ephemeralRunner)
|
||||||
|
Expect(err).To(BeNil(), "failed to create ephemeral runner")
|
||||||
|
|
||||||
|
Eventually(
|
||||||
|
func() bool {
|
||||||
|
return serverSuccessfullyCalled
|
||||||
|
},
|
||||||
|
2*time.Second,
|
||||||
|
interval,
|
||||||
|
).Should(BeTrue(), "failed to contact server")
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -450,6 +450,22 @@ func (r *EphemeralRunnerSetReconciler) actionsClientFor(ctx context.Context, rs
|
||||||
if err := r.Get(ctx, types.NamespacedName{Namespace: rs.Namespace, Name: rs.Spec.EphemeralRunnerSpec.GitHubConfigSecret}, secret); err != nil {
|
if err := r.Get(ctx, types.NamespacedName{Namespace: rs.Namespace, Name: rs.Spec.EphemeralRunnerSpec.GitHubConfigSecret}, secret); err != nil {
|
||||||
return nil, fmt.Errorf("failed to get secret: %w", err)
|
return nil, fmt.Errorf("failed to get secret: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
opts, err := r.actionsClientOptionsFor(ctx, rs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get actions client options: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.ActionsClient.GetClientFromSecret(
|
||||||
|
ctx,
|
||||||
|
rs.Spec.EphemeralRunnerSpec.GitHubConfigUrl,
|
||||||
|
rs.Namespace,
|
||||||
|
secret.Data,
|
||||||
|
opts...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EphemeralRunnerSetReconciler) actionsClientOptionsFor(ctx context.Context, rs *v1alpha1.EphemeralRunnerSet) ([]actions.ClientOption, error) {
|
||||||
var opts []actions.ClientOption
|
var opts []actions.ClientOption
|
||||||
if rs.Spec.EphemeralRunnerSpec.Proxy != nil {
|
if rs.Spec.EphemeralRunnerSpec.Proxy != nil {
|
||||||
proxyFunc, err := rs.Spec.EphemeralRunnerSpec.Proxy.ProxyFunc(func(s string) (*corev1.Secret, error) {
|
proxyFunc, err := rs.Spec.EphemeralRunnerSpec.Proxy.ProxyFunc(func(s string) (*corev1.Secret, error) {
|
||||||
|
|
@ -468,13 +484,32 @@ func (r *EphemeralRunnerSetReconciler) actionsClientFor(ctx context.Context, rs
|
||||||
opts = append(opts, actions.WithProxy(proxyFunc))
|
opts = append(opts, actions.WithProxy(proxyFunc))
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.ActionsClient.GetClientFromSecret(
|
tlsConfig := rs.Spec.EphemeralRunnerSpec.GitHubServerTLS
|
||||||
ctx,
|
if tlsConfig != nil {
|
||||||
rs.Spec.EphemeralRunnerSpec.GitHubConfigUrl,
|
pool, err := tlsConfig.ToCertPool(func(name, key string) ([]byte, error) {
|
||||||
rs.Namespace,
|
var configmap corev1.ConfigMap
|
||||||
secret.Data,
|
err := r.Get(
|
||||||
opts...,
|
ctx,
|
||||||
)
|
types.NamespacedName{
|
||||||
|
Namespace: rs.Namespace,
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
&configmap,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get configmap %s: %w", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(configmap.Data[key]), nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get tls config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts = append(opts, actions.WithRootCAs(pool))
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupWithManager sets up the controller with the Manager.
|
// SetupWithManager sets up the controller with the Manager.
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,13 @@ package actionsgithubcom
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -24,6 +27,7 @@ import (
|
||||||
v1alpha1 "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
v1alpha1 "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
"github.com/actions/actions-runner-controller/github/actions"
|
||||||
"github.com/actions/actions-runner-controller/github/actions/fake"
|
"github.com/actions/actions-runner-controller/github/actions/fake"
|
||||||
|
"github.com/actions/actions-runner-controller/github/actions/testserver"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -834,3 +838,148 @@ var _ = Describe("Test EphemeralRunnerSet controller with proxy settings", func(
|
||||||
).Should(BeEquivalentTo(true))
|
).Should(BeEquivalentTo(true))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var _ = Describe("Test EphemeralRunnerSet controller with custom root CA", func() {
|
||||||
|
var ctx context.Context
|
||||||
|
var mgr ctrl.Manager
|
||||||
|
var autoscalingNS *corev1.Namespace
|
||||||
|
var ephemeralRunnerSet *actionsv1alpha1.EphemeralRunnerSet
|
||||||
|
var configSecret *corev1.Secret
|
||||||
|
var rootCAConfigMap *corev1.ConfigMap
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
ctx = context.Background()
|
||||||
|
autoscalingNS, mgr = createNamespace(GinkgoT(), k8sClient)
|
||||||
|
configSecret = createDefaultSecret(GinkgoT(), k8sClient, autoscalingNS.Name)
|
||||||
|
|
||||||
|
cert, err := os.ReadFile(filepath.Join(
|
||||||
|
"../../",
|
||||||
|
"github",
|
||||||
|
"actions",
|
||||||
|
"testdata",
|
||||||
|
"rootCA.crt",
|
||||||
|
))
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to read root CA cert")
|
||||||
|
rootCAConfigMap = &corev1.ConfigMap{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "root-ca-configmap",
|
||||||
|
Namespace: autoscalingNS.Name,
|
||||||
|
},
|
||||||
|
Data: map[string]string{
|
||||||
|
"rootCA.crt": string(cert),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = k8sClient.Create(ctx, rootCAConfigMap)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to create configmap with root CAs")
|
||||||
|
|
||||||
|
controller := &EphemeralRunnerSetReconciler{
|
||||||
|
Client: mgr.GetClient(),
|
||||||
|
Scheme: mgr.GetScheme(),
|
||||||
|
Log: logf.Log,
|
||||||
|
ActionsClient: actions.NewMultiClient("test", logr.Discard()),
|
||||||
|
}
|
||||||
|
err = controller.SetupWithManager(mgr)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to setup controller")
|
||||||
|
|
||||||
|
startManagers(GinkgoT(), mgr)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should be able to make requests to a server using root CAs", func() {
|
||||||
|
certsFolder := filepath.Join(
|
||||||
|
"../../",
|
||||||
|
"github",
|
||||||
|
"actions",
|
||||||
|
"testdata",
|
||||||
|
)
|
||||||
|
certPath := filepath.Join(certsFolder, "server.crt")
|
||||||
|
keyPath := filepath.Join(certsFolder, "server.key")
|
||||||
|
|
||||||
|
serverSuccessfullyCalled := false
|
||||||
|
server := testserver.NewUnstarted(GinkgoT(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
serverSuccessfullyCalled = true
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to load server cert")
|
||||||
|
|
||||||
|
server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||||
|
server.StartTLS()
|
||||||
|
|
||||||
|
ephemeralRunnerSet = &actionsv1alpha1.EphemeralRunnerSet{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-asrs",
|
||||||
|
Namespace: autoscalingNS.Name,
|
||||||
|
},
|
||||||
|
Spec: actionsv1alpha1.EphemeralRunnerSetSpec{
|
||||||
|
Replicas: 1,
|
||||||
|
EphemeralRunnerSpec: actionsv1alpha1.EphemeralRunnerSpec{
|
||||||
|
GitHubConfigUrl: server.ConfigURLForOrg("my-org"),
|
||||||
|
GitHubConfigSecret: configSecret.Name,
|
||||||
|
GitHubServerTLS: &actionsv1alpha1.GitHubServerTLSConfig{
|
||||||
|
CertificateFrom: &v1alpha1.TLSCertificateSource{
|
||||||
|
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||||
|
LocalObjectReference: corev1.LocalObjectReference{
|
||||||
|
Name: rootCAConfigMap.Name,
|
||||||
|
},
|
||||||
|
Key: "rootCA.crt",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
RunnerScaleSetId: 100,
|
||||||
|
PodTemplateSpec: corev1.PodTemplateSpec{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "runner",
|
||||||
|
Image: "ghcr.io/actions/runner",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = k8sClient.Create(ctx, ephemeralRunnerSet)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to create EphemeralRunnerSet")
|
||||||
|
|
||||||
|
runnerList := new(actionsv1alpha1.EphemeralRunnerList)
|
||||||
|
Eventually(func() (int, error) {
|
||||||
|
err := k8sClient.List(ctx, runnerList, client.InNamespace(ephemeralRunnerSet.Namespace))
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(runnerList.Items), nil
|
||||||
|
},
|
||||||
|
ephemeralRunnerSetTestTimeout,
|
||||||
|
ephemeralRunnerSetTestInterval,
|
||||||
|
).Should(BeEquivalentTo(1), "failed to create ephemeral runner")
|
||||||
|
|
||||||
|
runner := runnerList.Items[0].DeepCopy()
|
||||||
|
Expect(runner.Spec.GitHubServerTLS).NotTo(BeNil(), "runner tls config should not be nil")
|
||||||
|
Expect(runner.Spec.GitHubServerTLS).To(BeEquivalentTo(ephemeralRunnerSet.Spec.EphemeralRunnerSpec.GitHubServerTLS), "runner tls config should be correct")
|
||||||
|
|
||||||
|
runner.Status.Phase = corev1.PodRunning
|
||||||
|
runner.Status.RunnerId = 100
|
||||||
|
err = k8sClient.Status().Patch(ctx, runner, client.MergeFrom(&runnerList.Items[0]))
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to update ephemeral runner status")
|
||||||
|
|
||||||
|
updatedRunnerSet := new(actionsv1alpha1.EphemeralRunnerSet)
|
||||||
|
err = k8sClient.Get(ctx, client.ObjectKey{Namespace: ephemeralRunnerSet.Namespace, Name: ephemeralRunnerSet.Name}, updatedRunnerSet)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to get EphemeralRunnerSet")
|
||||||
|
|
||||||
|
updatedRunnerSet.Spec.Replicas = 0
|
||||||
|
err = k8sClient.Update(ctx, updatedRunnerSet)
|
||||||
|
Expect(err).NotTo(HaveOccurred(), "failed to update EphemeralRunnerSet")
|
||||||
|
|
||||||
|
// wait for server to be called
|
||||||
|
Eventually(
|
||||||
|
func() bool {
|
||||||
|
return serverSuccessfullyCalled
|
||||||
|
},
|
||||||
|
autoscalingRunnerSetTestTimeout,
|
||||||
|
1*time.Nanosecond,
|
||||||
|
).Should(BeTrue(), "server was not called")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
||||||
|
|
@ -307,6 +307,7 @@ func (b *resourceBuilder) newAutoScalingListener(autoscalingRunnerSet *v1alpha1.
|
||||||
Image: image,
|
Image: image,
|
||||||
ImagePullSecrets: imagePullSecrets,
|
ImagePullSecrets: imagePullSecrets,
|
||||||
Proxy: autoscalingRunnerSet.Spec.Proxy,
|
Proxy: autoscalingRunnerSet.Spec.Proxy,
|
||||||
|
GitHubServerTLS: autoscalingRunnerSet.Spec.GitHubServerTLS,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,11 @@ import (
|
||||||
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
||||||
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
|
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
|
||||||
|
|
||||||
var cfg *rest.Config
|
var (
|
||||||
var k8sClient client.Client
|
cfg *rest.Config
|
||||||
var testEnv *envtest.Environment
|
k8sClient client.Client
|
||||||
|
testEnv *envtest.Environment
|
||||||
|
)
|
||||||
|
|
||||||
func TestAPIs(t *testing.T) {
|
func TestAPIs(t *testing.T) {
|
||||||
RegisterFailHandler(Fail)
|
RegisterFailHandler(Fail)
|
||||||
|
|
|
||||||
|
|
@ -58,12 +58,6 @@ func newActionsServer(t *testing.T, handler http.Handler, options ...actionsServ
|
||||||
|
|
||||||
type actionsServerOption func(*actionsServer)
|
type actionsServerOption func(*actionsServer)
|
||||||
|
|
||||||
func withActionsToken(token string) actionsServerOption {
|
|
||||||
return func(s *actionsServer) {
|
|
||||||
s.token = token
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type actionsServer struct {
|
type actionsServer struct {
|
||||||
*httptest.Server
|
*httptest.Server
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -198,6 +198,12 @@ func (c *Client) Identifier() string {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.rootCAs != nil {
|
||||||
|
// ignoring because this cert pool is intended not to come from SystemCertPool
|
||||||
|
// nolint:staticcheck
|
||||||
|
identifier += fmt.Sprintf("rootCAs:%q", c.rootCAs.Subjects())
|
||||||
|
}
|
||||||
|
|
||||||
return uuid.NewHash(sha256.New(), uuid.NameSpaceOID, []byte(identifier), 6).String()
|
return uuid.NewHash(sha256.New(), uuid.NameSpaceOID, []byte(identifier), 6).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,8 +95,8 @@ func TestServerWithSelfSignedCertificates(t *testing.T) {
|
||||||
cert, err := os.ReadFile(filepath.Join("testdata", "rootCA.crt"))
|
cert, err := os.ReadFile(filepath.Join("testdata", "rootCA.crt"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
pool, err := actions.RootCAsFromConfigMap(map[string][]byte{"cert": cert})
|
pool := x509.NewCertPool()
|
||||||
require.NoError(t, err)
|
require.True(t, pool.AppendCertsFromPEM(cert))
|
||||||
|
|
||||||
client, err := actions.NewClient(configURL, auth, actions.WithRootCAs(pool))
|
client, err := actions.NewClient(configURL, auth, actions.WithRootCAs(pool))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -123,8 +123,8 @@ func TestServerWithSelfSignedCertificates(t *testing.T) {
|
||||||
cert, err := os.ReadFile(filepath.Join("testdata", "intermediate.pem"))
|
cert, err := os.ReadFile(filepath.Join("testdata", "intermediate.pem"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
pool, err := actions.RootCAsFromConfigMap(map[string][]byte{"cert": cert})
|
pool := x509.NewCertPool()
|
||||||
require.NoError(t, err)
|
require.True(t, pool.AppendCertsFromPEM(cert))
|
||||||
|
|
||||||
client, err := actions.NewClient(configURL, auth, actions.WithRootCAs(pool), actions.WithRetryMax(0))
|
client, err := actions.NewClient(configURL, auth, actions.WithRootCAs(pool), actions.WithRetryMax(0))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
"github.com/actions/actions-runner-controller/github/actions"
|
||||||
|
"github.com/actions/actions-runner-controller/github/actions/testserver"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
@ -95,9 +96,9 @@ func TestNewActionsServiceRequest(t *testing.T) {
|
||||||
t.Run("manages authentication", func(t *testing.T) {
|
t.Run("manages authentication", func(t *testing.T) {
|
||||||
t.Run("client is brand new", func(t *testing.T) {
|
t.Run("client is brand new", func(t *testing.T) {
|
||||||
token := defaultActionsToken(t)
|
token := defaultActionsToken(t)
|
||||||
server := newActionsServer(t, nil, withActionsToken(token))
|
server := testserver.New(t, nil, testserver.WithActionsToken(token))
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), defaultCreds)
|
client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
req, err := client.NewActionsServiceRequest(ctx, http.MethodGet, "my-path", nil)
|
req, err := client.NewActionsServiceRequest(ctx, http.MethodGet, "my-path", nil)
|
||||||
|
|
@ -108,9 +109,9 @@ func TestNewActionsServiceRequest(t *testing.T) {
|
||||||
|
|
||||||
t.Run("admin token is about to expire", func(t *testing.T) {
|
t.Run("admin token is about to expire", func(t *testing.T) {
|
||||||
newToken := defaultActionsToken(t)
|
newToken := defaultActionsToken(t)
|
||||||
server := newActionsServer(t, nil, withActionsToken(newToken))
|
server := testserver.New(t, nil, testserver.WithActionsToken(newToken))
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), defaultCreds)
|
client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
client.ActionsServiceAdminToken = "expiring-token"
|
client.ActionsServiceAdminToken = "expiring-token"
|
||||||
client.ActionsServiceAdminTokenExpiresAt = time.Now().Add(59 * time.Second)
|
client.ActionsServiceAdminTokenExpiresAt = time.Now().Add(59 * time.Second)
|
||||||
|
|
@ -123,9 +124,9 @@ func TestNewActionsServiceRequest(t *testing.T) {
|
||||||
|
|
||||||
t.Run("token is currently valid", func(t *testing.T) {
|
t.Run("token is currently valid", func(t *testing.T) {
|
||||||
tokenThatShouldNotBeFetched := defaultActionsToken(t)
|
tokenThatShouldNotBeFetched := defaultActionsToken(t)
|
||||||
server := newActionsServer(t, nil, withActionsToken(tokenThatShouldNotBeFetched))
|
server := testserver.New(t, nil, testserver.WithActionsToken(tokenThatShouldNotBeFetched))
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), defaultCreds)
|
client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
client.ActionsServiceAdminToken = "healthy-token"
|
client.ActionsServiceAdminToken = "healthy-token"
|
||||||
client.ActionsServiceAdminTokenExpiresAt = time.Now().Add(1 * time.Hour)
|
client.ActionsServiceAdminTokenExpiresAt = time.Now().Add(1 * time.Hour)
|
||||||
|
|
@ -138,9 +139,9 @@ func TestNewActionsServiceRequest(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("builds the right URL including api version", func(t *testing.T) {
|
t.Run("builds the right URL including api version", func(t *testing.T) {
|
||||||
server := newActionsServer(t, nil)
|
server := testserver.New(t, nil)
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), defaultCreds)
|
client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
req, err := client.NewActionsServiceRequest(ctx, http.MethodGet, "/my/path?name=banana", nil)
|
req, err := client.NewActionsServiceRequest(ctx, http.MethodGet, "/my/path?name=banana", nil)
|
||||||
|
|
@ -157,9 +158,9 @@ func TestNewActionsServiceRequest(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("populates header", func(t *testing.T) {
|
t.Run("populates header", func(t *testing.T) {
|
||||||
server := newActionsServer(t, nil)
|
server := testserver.New(t, nil)
|
||||||
|
|
||||||
client, err := actions.NewClient(server.configURLForOrg("my-org"), defaultCreds, actions.WithUserAgent("my-agent"))
|
client, err := actions.NewClient(server.ConfigURLForOrg("my-org"), defaultCreds, actions.WithUserAgent("my-agent"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
req, err := client.NewActionsServiceRequest(ctx, http.MethodGet, "/my/path", nil)
|
req, err := client.NewActionsServiceRequest(ctx, http.MethodGet, "/my/path", nil)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package actions_test
|
package actions_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/x509"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/github/actions"
|
"github.com/actions/actions-runner-controller/github/actions"
|
||||||
|
|
@ -108,4 +111,48 @@ func TestClient_Identifier(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("changes in TLS config", func(t *testing.T) {
|
||||||
|
configURL := "https://github.com/org/repo"
|
||||||
|
defaultCreds := &actions.ActionsAuth{
|
||||||
|
Token: "token",
|
||||||
|
}
|
||||||
|
|
||||||
|
noTlS, err := actions.NewClient(configURL, defaultCreds)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
poolFromCert := func(t *testing.T, path string) *x509.CertPool {
|
||||||
|
t.Helper()
|
||||||
|
f, err := os.ReadFile(path)
|
||||||
|
require.NoError(t, err)
|
||||||
|
pool := x509.NewCertPool()
|
||||||
|
require.True(t, pool.AppendCertsFromPEM(f))
|
||||||
|
return pool
|
||||||
|
}
|
||||||
|
|
||||||
|
root, err := actions.NewClient(
|
||||||
|
configURL,
|
||||||
|
defaultCreds,
|
||||||
|
actions.WithRootCAs(poolFromCert(t, filepath.Join("testdata", "rootCA.crt"))),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
chain, err := actions.NewClient(
|
||||||
|
configURL,
|
||||||
|
defaultCreds,
|
||||||
|
actions.WithRootCAs(poolFromCert(t, filepath.Join("testdata", "intermediate.pem"))),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
clients := []*actions.Client{
|
||||||
|
noTlS,
|
||||||
|
root,
|
||||||
|
chain,
|
||||||
|
}
|
||||||
|
identifiers := map[string]struct{}{}
|
||||||
|
for _, client := range clients {
|
||||||
|
identifiers[client.Identifier()] = struct{}{}
|
||||||
|
}
|
||||||
|
assert.Len(t, identifiers, len(clients), "all clients should have a unique identifier")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package actions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/x509"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -84,7 +83,7 @@ func (m *multiClient) GetClientFor(ctx context.Context, githubConfigURL string,
|
||||||
}
|
}
|
||||||
|
|
||||||
cachedClient, has := m.clients[key]
|
cachedClient, has := m.clients[key]
|
||||||
if has {
|
if has && cachedClient.rootCAs.Equal(client.rootCAs) {
|
||||||
m.logger.Info("using cache client", "githubConfigURL", githubConfigURL, "namespace", namespace)
|
m.logger.Info("using cache client", "githubConfigURL", githubConfigURL, "namespace", namespace)
|
||||||
return cachedClient, nil
|
return cachedClient, nil
|
||||||
}
|
}
|
||||||
|
|
@ -141,19 +140,3 @@ func (m *multiClient) GetClientFromSecret(ctx context.Context, githubConfigURL,
|
||||||
auth.AppCreds = &GitHubAppAuth{AppID: parsedAppID, AppInstallationID: parsedAppInstallationID, AppPrivateKey: appPrivateKey}
|
auth.AppCreds = &GitHubAppAuth{AppID: parsedAppID, AppInstallationID: parsedAppInstallationID, AppPrivateKey: appPrivateKey}
|
||||||
return m.GetClientFor(ctx, githubConfigURL, auth, namespace, options...)
|
return m.GetClientFor(ctx, githubConfigURL, auth, namespace, options...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RootCAsFromConfigMap(configMapData map[string][]byte) (*x509.CertPool, error) {
|
|
||||||
caCertPool, err := x509.SystemCertPool()
|
|
||||||
if err != nil {
|
|
||||||
caCertPool = x509.NewCertPool()
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, certData := range configMapData {
|
|
||||||
ok := caCertPool.AppendCertsFromPEM(certData)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("no certificates successfully parsed from key %s", key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return caCertPool, nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue