feat: Make TLS to repo-server configurable and optional (#5764)

feat:  Make TLS to repo-server configurable and optional (#5764)

Signed-off-by: jannfis <jann@mistrust.net>
This commit is contained in:
jannfis
2021-03-16 17:23:10 +01:00
committed by GitHub
parent 9c849e63f7
commit 7a68880e2e
24 changed files with 567 additions and 33 deletions

View File

@@ -2,6 +2,7 @@ package commands
import (
"context"
"fmt"
"math"
"time"
@@ -26,6 +27,7 @@ import (
"github.com/argoproj/argo-cd/util/errors"
kubeutil "github.com/argoproj/argo-cd/util/kube"
"github.com/argoproj/argo-cd/util/settings"
"github.com/argoproj/argo-cd/util/tls"
)
const (
@@ -50,6 +52,8 @@ func NewCommand() *cobra.Command {
kubectlParallelismLimit int64
cacheSrc func() (*appstatecache.Cache, error)
redisClient *redis.Client
repoServerPlaintext bool
repoServerStrictTLS bool
)
var command = cobra.Command{
Use: cliName,
@@ -72,7 +76,26 @@ func NewCommand() *cobra.Command {
errors.CheckError(err)
resyncDuration := time.Duration(appResyncPeriod) * time.Second
repoClientset := apiclient.NewRepoServerClientset(repoServerAddress, repoServerTimeoutSeconds)
tlsConfig := apiclient.TLSConfiguration{
DisableTLS: repoServerPlaintext,
StrictValidation: repoServerStrictTLS,
}
// Load CA information to use for validating connections to the
// repository server, if strict TLS validation was requested.
if !repoServerPlaintext && repoServerStrictTLS {
pool, err := tls.LoadX509CertPool(
fmt.Sprintf("%s/controller/tls/tls.crt", env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)),
fmt.Sprintf("%s/controller/tls/ca.crt", env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)),
)
if err != nil {
log.Fatalf("%v", err)
}
tlsConfig.Certificates = pool
}
repoClientset := apiclient.NewRepoServerClientset(repoServerAddress, repoServerTimeoutSeconds, tlsConfig)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -126,6 +149,8 @@ func NewCommand() *cobra.Command {
command.Flags().DurationVar(&metricsCacheExpiration, "metrics-cache-expiration", 0*time.Second, "Prometheus metrics cache expiration (disabled by default. e.g. 24h0m0s)")
command.Flags().IntVar(&selfHealTimeoutSeconds, "self-heal-timeout-seconds", 5, "Specifies timeout between application self heal attempts")
command.Flags().Int64Var(&kubectlParallelismLimit, "kubectl-parallelism-limit", 20, "Number of allowed concurrent kubectl fork/execs. Any value less the 1 means no limit.")
command.Flags().BoolVar(&repoServerPlaintext, "repo-server-plaintext", false, "Disable TLS on connections to repo server")
command.Flags().BoolVar(&repoServerStrictTLS, "repo-server-strict-tls", false, "Whether to use strict validation of the TLS cert presented by the repo server")
cacheSrc = appstatecache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {
redisClient = client
})

View File

@@ -67,8 +67,10 @@ func NewCommand() *cobra.Command {
listenPort int
metricsPort int
cacheSrc func() (*reposervercache.Cache, error)
tlsConfigCustomizer tls.ConfigCustomizer
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
redisClient *redis.Client
disableTLS bool
)
var command = cobra.Command{
Use: cliName,
@@ -79,8 +81,11 @@ func NewCommand() *cobra.Command {
cli.SetLogFormat(cmdutil.LogFormat)
cli.SetLogLevel(cmdutil.LogLevel)
tlsConfigCustomizer, err := tlsConfigCustomizerSrc()
errors.CheckError(err)
if !disableTLS {
var err error
tlsConfigCustomizer, err = tlsConfigCustomizerSrc()
errors.CheckError(err)
}
cache, err := cacheSrc()
errors.CheckError(err)
@@ -104,7 +109,7 @@ func NewCommand() *cobra.Command {
// connect to itself to make sure repo server is able to serve connection
// used by liveness probe to auto restart repo server
// see https://github.com/argoproj/argo-cd/issues/5110 for more information
conn, err := apiclient.NewConnection(fmt.Sprintf("localhost:%d", listenPort), 60)
conn, err := apiclient.NewConnection(fmt.Sprintf("localhost:%d", listenPort), 60, &apiclient.TLSConfiguration{DisableTLS: disableTLS})
if err != nil {
return err
}
@@ -152,6 +157,7 @@ func NewCommand() *cobra.Command {
command.Flags().Int64Var(&parallelismLimit, "parallelismlimit", 0, "Limit on number of concurrent manifests generate requests. Any value less the 1 means no limit.")
command.Flags().IntVar(&listenPort, "port", common.DefaultPortRepoServer, "Listen on given port for incoming connections")
command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortRepoServerMetrics, "Start metrics server on given port")
command.Flags().BoolVar(&disableTLS, "disable-tls", false, "Disable TLS on the gRPC endpoint")
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(&command)
cacheSrc = reposervercache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {

View File

@@ -2,6 +2,7 @@ package commands
import (
"context"
"fmt"
"time"
"github.com/argoproj/pkg/stats"
@@ -60,6 +61,8 @@ func NewCommand() *cobra.Command {
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
cacheSrc func() (*servercache.Cache, error)
frameOptions string
repoServerPlaintext bool
repoServerStrictTLS bool
)
var command = &cobra.Command{
Use: cliName,
@@ -93,8 +96,25 @@ func NewCommand() *cobra.Command {
appclientsetConfig = kube.AddFailureRetryWrapper(appclientsetConfig, failureRetryCount, failureRetryPeriodMilliSeconds)
}
appclientset := appclientset.NewForConfigOrDie(appclientsetConfig)
repoclientset := apiclient.NewRepoServerClientset(repoServerAddress, repoServerTimeoutSeconds)
tlsConfig := apiclient.TLSConfiguration{
DisableTLS: repoServerPlaintext,
StrictValidation: repoServerStrictTLS,
}
// Load CA information to use for validating connections to the
// repository server, if strict TLS validation was requested.
if !repoServerPlaintext && repoServerStrictTLS {
pool, err := tls.LoadX509CertPool(
fmt.Sprintf("%s/server/tls/tls.crt", env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)),
fmt.Sprintf("%s/server/tls/ca.crt", env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath)),
)
if err != nil {
log.Fatalf("%v", err)
}
tlsConfig.Certificates = pool
}
repoclientset := apiclient.NewRepoServerClientset(repoServerAddress, repoServerTimeoutSeconds, tlsConfig)
if rootPath != "" {
if baseHRef != "" && baseHRef != rootPath {
log.Warnf("--basehref and --rootpath had conflict: basehref: %s rootpath: %s", baseHRef, rootPath)
@@ -153,6 +173,8 @@ func NewCommand() *cobra.Command {
command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortArgoCDAPIServerMetrics, "Start metrics on given port")
command.Flags().IntVar(&repoServerTimeoutSeconds, "repo-server-timeout-seconds", 60, "Repo server RPC call timeout seconds.")
command.Flags().StringVar(&frameOptions, "x-frame-options", "sameorigin", "Set X-Frame-Options header in HTTP responses to `value`. To disable, set to \"\".")
command.Flags().BoolVar(&repoServerPlaintext, "repo-server-plaintext", false, "Use a plaintext client (non-TLS) to connect to repository server")
command.Flags().BoolVar(&repoServerStrictTLS, "repo-server-strict-tls", false, "Perform strict validation of TLS certificates when connecting to repo server")
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(command)
cacheSrc = servercache.AddCacheFlagsToCmd(command, func(client *redis.Client) {
redisClient = client

View File

@@ -251,7 +251,7 @@ func NewReconcileCommand() *cobra.Command {
errors.CheckError(err)
repoServerAddress = fmt.Sprintf("localhost:%d", repoServerPort)
}
repoServerClient := apiclient.NewRepoServerClientset(repoServerAddress, 60)
repoServerClient := apiclient.NewRepoServerClientset(repoServerAddress, 60, apiclient.TLSConfiguration{DisableTLS: false, StrictValidation: false})
appClientset := appclientset.NewForConfigOrDie(cfg)
kubeClientset := kubernetes.NewForConfigOrDie(cfg)

View File

@@ -53,6 +53,8 @@ const (
DefaultSSHKnownHostsName = "ssh_known_hosts"
// Default path to GnuPG home directory
DefaultGnuPgHomePath = "/app/config/gpg/keys"
// Default path to repo server TLS endpoint config
DefaultAppConfigPath = "/app/config"
)
const (
@@ -196,6 +198,8 @@ const (
EnvGithubAppCredsExpirationDuration = "ARGOCD_GITHUB_APP_CREDS_EXPIRATION_DURATION"
// EnvHelmIndexCacheDuration controls how the helm repository index file is cached for (default: 0)
EnvHelmIndexCacheDuration = "ARGOCD_HELM_INDEX_CACHE_DURATION"
// EnvRepoServerConfigPath allows to override the configuration path for repo server
EnvAppConfigPath = "ARGOCD_APP_CONF_PATH"
)
const (

View File

@@ -38,6 +38,8 @@ argocd-application-controller [flags]
--redis string Redis server hostname and port (e.g. argocd-redis:6379).
--redisdb int Redis database.
--repo-server string Repo server address. (default "argocd-repo-server:8081")
--repo-server-plaintext Disable TLS on connections to repo server
--repo-server-strict-tls Whether to use strict validation of the TLS cert presented by the repo server
--repo-server-timeout-seconds int Repo server RPC call timeout seconds. (default 60)
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
--self-heal-timeout-seconds int Specifies timeout between application self heal attempts (default 5)

View File

@@ -14,6 +14,7 @@ argocd-repo-server [flags]
```
--default-cache-expiration duration Cache expiration default (default 24h0m0s)
--disable-tls Disable TLS on the gRPC endpoint
-h, --help help for argocd-repo-server
--logformat string Set the logging format. One of: text|json (default "text")
--loglevel string Set the logging level. One of: debug|info|warn|error (default "info")

View File

@@ -43,6 +43,8 @@ argocd-server [flags]
--redis string Redis server hostname and port (e.g. argocd-redis:6379).
--redisdb int Redis database.
--repo-server string Repo server address (default "argocd-repo-server:8081")
--repo-server-plaintext Use a plaintext client (non-TLS) to connect to repository server
--repo-server-strict-tls Perform strict validation of TLS certificates when connecting to repo server
--repo-server-timeout-seconds int Repo server RPC call timeout seconds. (default 60)
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
--rootpath string Used if Argo CD is running behind reverse proxy under subpath different from /

View File

@@ -41,6 +41,9 @@ spec:
port: 8082
initialDelaySeconds: 5
periodSeconds: 10
volumeMounts:
- name: argocd-repo-server-tls
mountPath: /app/config/controller/tls
serviceAccountName: argocd-application-controller
affinity:
podAntiAffinity:
@@ -57,3 +60,15 @@ spec:
matchLabels:
app.kubernetes.io/part-of: argocd
topologyKey: kubernetes.io/hostname
volumes:
- name: argocd-repo-server-tls
secret:
secretName: argocd-repo-server-tls
optional: true
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
- key: ca.crt
path: ca.crt

View File

@@ -50,6 +50,8 @@ spec:
mountPath: /app/config/gpg/source
- name: gpg-keyring
mountPath: /app/config/gpg/keys
- name: argocd-repo-server-tls
mountPath: /app/config/reposerver/tls
volumes:
- name: ssh-known-hosts
configMap:
@@ -62,6 +64,17 @@ spec:
name: argocd-gpg-keys-cm
- name: gpg-keyring
emptyDir: {}
- name: argocd-repo-server-tls
secret:
secretName: argocd-repo-server-tls
optional: true
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
- key: ca.crt
path: ca.crt
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:

View File

@@ -26,6 +26,8 @@ spec:
mountPath: /app/config/ssh
- name: tls-certs
mountPath: /app/config/tls
- name: argocd-repo-server-tls
mountPath: /app/config/server/tls
ports:
- containerPort: 8080
- containerPort: 8083
@@ -50,6 +52,17 @@ spec:
- name: tls-certs
configMap:
name: argocd-tls-certs-cm
- name: argocd-repo-server-tls
secret:
secretName: argocd-repo-server-tls
optional: true
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
- key: ca.crt
path: ca.crt
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:

View File

@@ -3338,6 +3338,8 @@ spec:
name: gpg-keys
- mountPath: /app/config/gpg/keys
name: gpg-keyring
- mountPath: /app/config/reposerver/tls
name: argocd-repo-server-tls
volumes:
- configMap:
name: argocd-ssh-known-hosts-cm
@@ -3350,6 +3352,17 @@ spec:
name: gpg-keys
- emptyDir: {}
name: gpg-keyring
- name: argocd-repo-server-tls
secret:
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
- key: ca.crt
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
---
apiVersion: apps/v1
kind: Deployment
@@ -3416,6 +3429,8 @@ spec:
name: ssh-known-hosts
- mountPath: /app/config/tls
name: tls-certs
- mountPath: /app/config/server/tls
name: argocd-repo-server-tls
serviceAccountName: argocd-server
volumes:
- emptyDir: {}
@@ -3426,6 +3441,17 @@ spec:
- configMap:
name: argocd-tls-certs-cm
name: tls-certs
- name: argocd-repo-server-tls
secret:
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
- key: ca.crt
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
---
apiVersion: apps/v1
kind: StatefulSet
@@ -3487,7 +3513,22 @@ spec:
port: 8082
initialDelaySeconds: 5
periodSeconds: 10
volumeMounts:
- mountPath: /app/config/controller/tls
name: argocd-repo-server-tls
serviceAccountName: argocd-application-controller
volumes:
- name: argocd-repo-server-tls
secret:
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
- key: ca.crt
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
---
apiVersion: apps/v1
kind: StatefulSet

View File

@@ -3253,6 +3253,8 @@ spec:
name: gpg-keys
- mountPath: /app/config/gpg/keys
name: gpg-keyring
- mountPath: /app/config/reposerver/tls
name: argocd-repo-server-tls
volumes:
- configMap:
name: argocd-ssh-known-hosts-cm
@@ -3265,6 +3267,17 @@ spec:
name: gpg-keys
- emptyDir: {}
name: gpg-keyring
- name: argocd-repo-server-tls
secret:
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
- key: ca.crt
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
---
apiVersion: apps/v1
kind: Deployment
@@ -3331,6 +3344,8 @@ spec:
name: ssh-known-hosts
- mountPath: /app/config/tls
name: tls-certs
- mountPath: /app/config/server/tls
name: argocd-repo-server-tls
serviceAccountName: argocd-server
volumes:
- emptyDir: {}
@@ -3341,6 +3356,17 @@ spec:
- configMap:
name: argocd-tls-certs-cm
name: tls-certs
- name: argocd-repo-server-tls
secret:
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
- key: ca.crt
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
---
apiVersion: apps/v1
kind: StatefulSet
@@ -3402,7 +3428,22 @@ spec:
port: 8082
initialDelaySeconds: 5
periodSeconds: 10
volumeMounts:
- mountPath: /app/config/controller/tls
name: argocd-repo-server-tls
serviceAccountName: argocd-application-controller
volumes:
- name: argocd-repo-server-tls
secret:
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
- key: ca.crt
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
---
apiVersion: apps/v1
kind: StatefulSet

View File

@@ -2672,6 +2672,8 @@ spec:
name: gpg-keys
- mountPath: /app/config/gpg/keys
name: gpg-keyring
- mountPath: /app/config/reposerver/tls
name: argocd-repo-server-tls
volumes:
- configMap:
name: argocd-ssh-known-hosts-cm
@@ -2684,6 +2686,17 @@ spec:
name: gpg-keys
- emptyDir: {}
name: gpg-keyring
- name: argocd-repo-server-tls
secret:
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
- key: ca.crt
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
---
apiVersion: apps/v1
kind: Deployment
@@ -2745,6 +2758,8 @@ spec:
name: ssh-known-hosts
- mountPath: /app/config/tls
name: tls-certs
- mountPath: /app/config/server/tls
name: argocd-repo-server-tls
serviceAccountName: argocd-server
volumes:
- emptyDir: {}
@@ -2755,6 +2770,17 @@ spec:
- configMap:
name: argocd-tls-certs-cm
name: tls-certs
- name: argocd-repo-server-tls
secret:
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
- key: ca.crt
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
---
apiVersion: apps/v1
kind: StatefulSet
@@ -2814,4 +2840,19 @@ spec:
port: 8082
initialDelaySeconds: 5
periodSeconds: 10
volumeMounts:
- mountPath: /app/config/controller/tls
name: argocd-repo-server-tls
serviceAccountName: argocd-application-controller
volumes:
- name: argocd-repo-server-tls
secret:
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
- key: ca.crt
path: ca.crt
optional: true
secretName: argocd-repo-server-tls

View File

@@ -2587,6 +2587,8 @@ spec:
name: gpg-keys
- mountPath: /app/config/gpg/keys
name: gpg-keyring
- mountPath: /app/config/reposerver/tls
name: argocd-repo-server-tls
volumes:
- configMap:
name: argocd-ssh-known-hosts-cm
@@ -2599,6 +2601,17 @@ spec:
name: gpg-keys
- emptyDir: {}
name: gpg-keyring
- name: argocd-repo-server-tls
secret:
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
- key: ca.crt
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
---
apiVersion: apps/v1
kind: Deployment
@@ -2660,6 +2673,8 @@ spec:
name: ssh-known-hosts
- mountPath: /app/config/tls
name: tls-certs
- mountPath: /app/config/server/tls
name: argocd-repo-server-tls
serviceAccountName: argocd-server
volumes:
- emptyDir: {}
@@ -2670,6 +2685,17 @@ spec:
- configMap:
name: argocd-tls-certs-cm
name: tls-certs
- name: argocd-repo-server-tls
secret:
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
- key: ca.crt
path: ca.crt
optional: true
secretName: argocd-repo-server-tls
---
apiVersion: apps/v1
kind: StatefulSet
@@ -2729,4 +2755,19 @@ spec:
port: 8082
initialDelaySeconds: 5
periodSeconds: 10
volumeMounts:
- mountPath: /app/config/controller/tls
name: argocd-repo-server-tls
serviceAccountName: argocd-application-controller
volumes:
- name: argocd-repo-server-tls
secret:
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
- key: ca.crt
path: ca.crt
optional: true
secretName: argocd-repo-server-tls

View File

@@ -2,6 +2,7 @@ package apiclient
import (
"crypto/tls"
"crypto/x509"
"time"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
@@ -19,6 +20,16 @@ const (
MaxGRPCMessageSize = 100 * 1024 * 1024
)
// TLSConfiguration describes parameters for TLS configuration to be used by a repo server API client
type TLSConfiguration struct {
// Whether to disable TLS for connections
DisableTLS bool
// Whether to enforce strict validation of TLS certificates
StrictValidation bool
// List of certificates to validate the peer against (if StrictCerts is true)
Certificates *x509.CertPool
}
// Clientset represents repository server api clients
type Clientset interface {
NewRepoServerClient() (io.Closer, RepoServerServiceClient, error)
@@ -27,17 +38,18 @@ type Clientset interface {
type clientSet struct {
address string
timeoutSeconds int
tlsConfig TLSConfiguration
}
func (c *clientSet) NewRepoServerClient() (io.Closer, RepoServerServiceClient, error) {
conn, err := NewConnection(c.address, c.timeoutSeconds)
conn, err := NewConnection(c.address, c.timeoutSeconds, &c.tlsConfig)
if err != nil {
return nil, nil, err
}
return conn, NewRepoServerServiceClient(conn), nil
}
func NewConnection(address string, timeoutSeconds int) (*grpc.ClientConn, error) {
func NewConnection(address string, timeoutSeconds int, tlsConfig *TLSConfiguration) (*grpc.ClientConn, error) {
retryOpts := []grpc_retry.CallOption{
grpc_retry.WithMax(3),
grpc_retry.WithBackoff(grpc_retry.BackoffLinear(1000 * time.Millisecond)),
@@ -47,12 +59,23 @@ func NewConnection(address string, timeoutSeconds int) (*grpc.ClientConn, error)
unaryInterceptors = append(unaryInterceptors, argogrpc.WithTimeout(time.Duration(timeoutSeconds)*time.Second))
}
opts := []grpc.DialOption{
grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})),
grpc.WithStreamInterceptor(grpc_retry.StreamClientInterceptor(retryOpts...)),
grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(unaryInterceptors...)),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxGRPCMessageSize), grpc.MaxCallSendMsgSize(MaxGRPCMessageSize)),
}
tlsC := &tls.Config{}
if !tlsConfig.DisableTLS {
if !tlsConfig.StrictValidation {
tlsC.InsecureSkipVerify = true
} else {
tlsC.RootCAs = tlsConfig.Certificates
}
opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsC)))
} else {
opts = append(opts, grpc.WithInsecure())
}
conn, err := grpc.Dial(address, opts...)
if err != nil {
log.Errorf("Unable to connect to repository service with address %s", address)
@@ -62,6 +85,6 @@ func NewConnection(address string, timeoutSeconds int) (*grpc.ClientConn, error)
}
// NewRepoServerClientset creates new instance of repo server Clientset
func NewRepoServerClientset(address string, timeoutSeconds int) Clientset {
return &clientSet{address: address, timeoutSeconds: timeoutSeconds}
func NewRepoServerClientset(address string, timeoutSeconds int, tlsConfig TLSConfiguration) Clientset {
return &clientSet{address: address, timeoutSeconds: timeoutSeconds, tlsConfig: tlsConfig}
}

View File

@@ -2,6 +2,7 @@ package reposerver
import (
"crypto/tls"
"fmt"
"os"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
@@ -21,6 +22,7 @@ import (
"github.com/argoproj/argo-cd/reposerver/metrics"
"github.com/argoproj/argo-cd/reposerver/repository"
"github.com/argoproj/argo-cd/server/version"
"github.com/argoproj/argo-cd/util/env"
grpc_util "github.com/argoproj/argo-cd/util/grpc"
tlsutil "github.com/argoproj/argo-cd/util/tls"
)
@@ -34,24 +36,25 @@ type ArgoCDRepoServer struct {
initConstants repository.RepoServerInitConstants
}
// The hostnames to generate self-signed issues with
var tlsHostList []string = []string{"localhost", "reposerver"}
// NewServer returns a new instance of the Argo CD Repo server
func NewServer(metricsServer *metrics.MetricsServer, cache *reposervercache.Cache, tlsConfCustomizer tlsutil.ConfigCustomizer, initConstants repository.RepoServerInitConstants) (*ArgoCDRepoServer, error) {
// generate TLS cert
hosts := []string{
"localhost",
"argocd-repo-server",
}
cert, err := tlsutil.GenerateX509KeyPair(tlsutil.CertOptions{
Hosts: hosts,
Organization: "Argo CD",
IsCA: true,
})
if err != nil {
return nil, err
}
var tlsConfig *tls.Config
tlsConfig := &tls.Config{Certificates: []tls.Certificate{*cert}}
tlsConfCustomizer(tlsConfig)
// Generate or load TLS server certificates to use with this instance of
// repository server.
if tlsConfCustomizer != nil {
var err error
certPath := fmt.Sprintf("%s/reposerver/tls/tls.crt", env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath))
keyPath := fmt.Sprintf("%s/reposerver/tls/tls.key", env.StringFromEnv(common.EnvAppConfigPath, common.DefaultAppConfigPath))
tlsConfig, err = tlsutil.CreateServerTLSConfig(certPath, keyPath, tlsHostList)
if err != nil {
return nil, err
}
tlsConfCustomizer(tlsConfig)
}
if os.Getenv(common.EnvEnableGRPCTimeHistogramEnv) == "true" {
grpc_prometheus.EnableHandlingTimeHistogram()
@@ -61,18 +64,25 @@ func NewServer(metricsServer *metrics.MetricsServer, cache *reposervercache.Cach
streamInterceptors := []grpc.StreamServerInterceptor{grpc_logrus.StreamServerInterceptor(serverLog), grpc_prometheus.StreamServerInterceptor, grpc_util.PanicLoggerStreamServerInterceptor(serverLog)}
unaryInterceptors := []grpc.UnaryServerInterceptor{grpc_logrus.UnaryServerInterceptor(serverLog), grpc_prometheus.UnaryServerInterceptor, grpc_util.PanicLoggerUnaryServerInterceptor(serverLog)}
serverOpts := []grpc.ServerOption{
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(unaryInterceptors...)),
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(streamInterceptors...)),
grpc.MaxRecvMsgSize(apiclient.MaxGRPCMessageSize),
grpc.MaxSendMsgSize(apiclient.MaxGRPCMessageSize),
}
// We do allow for non-TLS servers to be created, in case of mTLS will be
// implemented by e.g. a sidecar container.
if tlsConfig != nil {
serverOpts = append(serverOpts, grpc.Creds(credentials.NewTLS(tlsConfig)))
}
return &ArgoCDRepoServer{
log: serverLog,
metricsServer: metricsServer,
cache: cache,
initConstants: initConstants,
opts: []grpc.ServerOption{
grpc.Creds(credentials.NewTLS(tlsConfig)),
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(unaryInterceptors...)),
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(streamInterceptors...)),
grpc.MaxRecvMsgSize(apiclient.MaxGRPCMessageSize),
grpc.MaxSendMsgSize(apiclient.MaxGRPCMessageSize),
},
opts: serverOpts,
}, nil
}

8
util/env/env.go vendored
View File

@@ -61,3 +61,11 @@ func ParseDurationFromEnv(env string, defaultValue, min, max time.Duration) time
}
return dur
}
func StringFromEnv(env string, defaultValue string) string {
str := os.Getenv(env)
if str == "" {
return defaultValue
}
return str
}

0
util/tls/testdata/empty_tls.crt vendored Normal file
View File

0
util/tls/testdata/empty_tls.key vendored Normal file
View File

29
util/tls/testdata/valid_tls.crt vendored Normal file
View File

@@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIFAzCCAuugAwIBAgIUKPX+VoX8ak9DFlhr0RorfvPbk30wDQYJKoZIhvcNAQEL
BQAwETEPMA0GA1UEAwwGU29tZUNOMB4XDTIxMDMxNTA4NTIyN1oXDTIyMDMxNTA4
NTIyN1owETEPMA0GA1UEAwwGU29tZUNOMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEA5KAr7vEPbksus5Vf4SeRUx/ZSvqSQ3oILKjYR6TB8Sp/CG1jYoLo
leIAme/o9soPpQ9x/fhzLZUvEB35YFrcEzLyHA2Nl4GlaxOJtgwX94D/+alvssHA
2z3u6bE9jLNxpBOzzM0iDuAUzqYfahkCdbGuYqMAorWto/WuebbjZanu3RbQrWA6
WVQ0cPhAS2dDt/IKrYNpC7AQkvbNm69jntK8wlhM4BsS2kJS3ZD4IkLzF/Ro+f3D
AxRmkZyDD+ZMBhRXmbucF8ryxmWmXZwx3D9j0tBTsyj2SEiCqLJkTU/jhBgbdV8e
AI1vrkM5d5hQaoGrVXPDcYgJXKiCH6o+6nKO3626M1rLIA239ghnrBr87eboPQSt
aKOPNsgQ2BZaJlkOV4pov5ZAUO3hDeXmvMIXAc6sQnxMWcAw/0sTzy57l6pxhur5
zyfyfFTkjON5SNMLjj5ELuKsNIecX9Qp1m9IZ+YRnuAAx4eQP6kGgGREXzhNAQbk
apyoIqsTHjMVMD4s6nD9eWvmQVL/dm9UqNAxXapxlLc7uqq2uaZV7IZlPOGt40kI
LmJBOCjjgiQjBiNckYbpuHox4uRlo1dgjx7P5mSmf71YoiFqIKj3Q8nKvCQ9/y8A
pPV/TcCz9iUmAxp8cHbjkD/ed6gLfoNKyVef1U6Z/hHyCtQsMIrQmN0CAwEAAaNT
MFEwHQYDVR0OBBYEFBv58Vn4WBGvqvlZ/H4DZ9ofXzGpMB8GA1UdIwQYMBaAFBv5
8Vn4WBGvqvlZ/H4DZ9ofXzGpMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
BQADggIBACRHQhhUH5HEKWKpoSZUYa6Ph3v5g9/tEp8o4zYcfllBmAGSfBatVyOh
rKuCphNjyXOZ6oFRl8xFBi3rWEu3nZlYJHmb5wWd9Fe0P4xHGLGKayNl5JKKZ4dq
Ypkg4kVvvL+4RexOM6QGDKbwR/wApRAN1/uK2Dv/nuctgJP8tnHX4vGL68ljZyVS
tHcQFoVgmCgKvMlvw+Vs+Bj2lzQooPSjkHLKtA242uaUsj0eW4zUn8NWyQIzx/He
AAzJXmUtS/dmGbPCkrzNpYQT96QtbcLoeRbKjJViFIzti8EYF6k9mctLRoC+6LVn
aeKx2k+6tN9/NS9Pz+hzGyqaJa7jk9QBnhDfrIjXWplTJG5sBakDlpvy+56SUwvq
KEfFlDzcAgr09/4X8bBX1x+ty8DbpojQXrHKS/HjavodbPazJxfkshtG6NidcKjB
11cVHfAezwurMt3X3AD51JLXbIu9BTy+3KXHsAqv7lfqAa9CrEL5uQ3SL5ZFS8zs
+nmFRVzh6GYB+FNKfV6mH/QOXn9ijWW22V66+pqEpN70ctfX6FSQa4GqhHf60iJ6
a1SIursm+PW/vfkcFrsyzF+DE3gTQcm3WxozHYY21PvBj0Ghgem8Vvo2/O+5wahu
9APBQITX18SHUJ9lm8gLq5tYE7BqsfFZxV9CX4Dkz98mqZfbhWbM
-----END CERTIFICATE-----

52
util/tls/testdata/valid_tls.key vendored Normal file
View File

@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
G8rsta8CWDcRNPNWohci0uq3e7kJvgM=
-----END PRIVATE KEY-----

View File

@@ -10,7 +10,9 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"math/big"
"net"
"os"
@@ -329,3 +331,87 @@ func EncodeX509KeyPairString(cert tls.Certificate) (string, string) {
certpem, keypem := EncodeX509KeyPair(cert)
return string(certpem), string(keypem)
}
// LoadX509CertPool loads PEM data from a list of files, adds them to a CertPool
// and returns the resulting CertPool
func LoadX509CertPool(paths ...string) (*x509.CertPool, error) {
pool := x509.NewCertPool()
for _, path := range paths {
log.Infof("Loading CA information from %s and appending it to cert pool", path)
_, err := os.Stat(path)
if err != nil {
// We just ignore non-existing paths...
if errors.Is(err, os.ErrNotExist) {
continue
}
// ...but everything else is considered an error
return nil, fmt.Errorf("could not load TLS certificate: %v", err)
} else {
f, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failure to load TLS certificates from %s: %v", path, err)
}
if ok := pool.AppendCertsFromPEM(f); !ok {
return nil, fmt.Errorf("invalid cert data in %s", path)
}
}
}
return pool, nil
}
// CreateServerTLSConfig will provide a TLS configuration for a server. It will
// either use a certificate and key provided at tlsCertPath and tlsKeyPath, or
// if these are not given, will generate a self-signed certificate valid for
// the specified list of hosts. If hosts is nil or empty, self-signed cert
// creation will be disabled.
func CreateServerTLSConfig(tlsCertPath, tlsKeyPath string, hosts []string) (*tls.Config, error) {
var cert *tls.Certificate
var err error
tlsCertExists := false
tlsKeyExists := false
// If cert and key paths were specified, ensure they exist
if tlsCertPath != "" && tlsKeyPath != "" {
_, err = os.Stat(tlsCertPath)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
log.Warnf("could not read TLS cert from %s: %v", tlsCertPath, err)
}
} else {
tlsCertExists = true
}
_, err = os.Stat(tlsKeyPath)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
log.Warnf("could not read TLS cert from %s: %v", tlsKeyPath, err)
}
} else {
tlsKeyExists = true
}
}
if !tlsCertExists || !tlsKeyExists {
log.Infof("Generating self-signed gRPC TLS certificate for this session")
c, err := GenerateX509KeyPair(CertOptions{
Hosts: hosts,
Organization: "Argo CD",
IsCA: true,
})
if err != nil {
return nil, err
}
cert = c
} else {
log.Infof("Loading gRPC TLS configuration from cert=%s and key=%s", tlsCertPath, tlsKeyPath)
c, err := tls.LoadX509KeyPair(tlsCertPath, tlsKeyPath)
if err != nil {
return nil, fmt.Errorf("Unable to initalize gRPC TLS configuration with cert=%s and key=%s: %v", tlsCertPath, tlsKeyPath, err)
}
cert = &c
}
return &tls.Config{Certificates: []tls.Certificate{*cert}}, nil
}

View File

@@ -10,6 +10,7 @@ import (
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var chain = `-----BEGIN CERTIFICATE-----
@@ -341,3 +342,61 @@ func TestBestEffortSystemCertPool(t *testing.T) {
pool := BestEffortSystemCertPool()
assert.NotNil(t, pool)
}
func TestCreateServerTLSConfig(t *testing.T) {
t.Run("Configuration from a valid key/cert pair", func(t *testing.T) {
tlsc, err := CreateServerTLSConfig("testdata/valid_tls.crt", "testdata/valid_tls.key", []string{"localhost", "argocd-repo-server"})
require.NoError(t, err)
assert.Len(t, tlsc.Certificates, 1)
c, err := x509.ParseCertificate(tlsc.Certificates[0].Certificate[0])
require.NoError(t, err)
assert.Equal(t, "SomeCN", c.Subject.CommonName)
})
t.Run("Self-signed creation due to non-existing cert", func(t *testing.T) {
tlsc, err := CreateServerTLSConfig("testdata/invvalid_tls.crt", "testdata/invalid_tls.key", []string{"localhost", "argocd-repo-server"})
require.NoError(t, err)
assert.Len(t, tlsc.Certificates, 1)
c, err := x509.ParseCertificate(tlsc.Certificates[0].Certificate[0])
require.NoError(t, err)
assert.Equal(t, []string{"Argo CD"}, c.Subject.Organization)
})
t.Run("Self-signed creation fails due to hosts being nil", func(t *testing.T) {
tlsc, err := CreateServerTLSConfig("testdata/invvalid_tls.crt", "testdata/invalid_tls.key", nil)
require.Error(t, err)
assert.Nil(t, tlsc)
})
t.Run("Self-signed creation fails due to invalid certificates", func(t *testing.T) {
tlsc, err := CreateServerTLSConfig("testdata/empty_tls.crt", "testdata/empty_tls.key", nil)
require.Error(t, err)
assert.Nil(t, tlsc)
})
}
func TestLoadX509CertPool(t *testing.T) {
t.Run("Successfully load single cert", func(t *testing.T) {
p, err := LoadX509CertPool("testdata/valid_tls.crt")
require.NoError(t, err)
require.NotNil(t, p)
assert.Len(t, p.Subjects(), 1)
})
t.Run("Successfully load single existing cert from multiple list", func(t *testing.T) {
p, err := LoadX509CertPool("testdata/invalid_tls.crt", "testdata/valid_tls.crt")
require.NoError(t, err)
require.NotNil(t, p)
assert.Len(t, p.Subjects(), 1)
})
t.Run("Only non-existing certs in list", func(t *testing.T) {
p, err := LoadX509CertPool("testdata/invalid_tls.crt", "testdata/valid_tls2.crt")
require.NoError(t, err)
require.NotNil(t, p)
assert.Len(t, p.Subjects(), 0)
})
t.Run("Invalid cert in requested cert list", func(t *testing.T) {
p, err := LoadX509CertPool("testdata/empty_tls.crt", "testdata/valid_tls2.crt")
require.Error(t, err)
require.Nil(t, p)
})
}