Add support for connecting repositories using TLS client certs (fixes #1945) (#1960)

This commit is contained in:
jannfis 2019-07-25 02:25:27 +02:00 committed by Alex Collins
parent 754e4754eb
commit 5953080c96
42 changed files with 1658 additions and 484 deletions

View file

@ -239,12 +239,16 @@ jobs:
environment:
# pft. if you do not quote "true", CircleCI turns it into "1", stoopid
ARGOCD_FAKE_IN_CLUSTER: "true"
ARGOCD_SSH_DATA_PATH: "/tmp/argo-e2e/app/config/ssh"
ARGOCD_TLS_DATA_PATH: "/tmp/argo-e2e/app/config/tls"
- run:
name: Start API server
command: go run ./cmd/argocd-server/main.go --loglevel debug --redis localhost:6379 --insecure --dex-server http://localhost:5556 --repo-server localhost:8081 --staticassets ../argo-cd-ui/dist/app
background: true
environment:
ARGOCD_FAKE_IN_CLUSTER: "true"
ARGOCD_SSH_DATA_PATH: "/tmp/argo-e2e/app/config/ssh"
ARGOCD_TLS_DATA_PATH: "/tmp/argo-e2e/app/config/tls"
- run:
name: Start Test Git
command: |

View file

@ -174,6 +174,10 @@ start-e2e: cli
kubectl create ns argocd-e2e || true
kubectl config set-context --current --namespace=argocd-e2e
kustomize build test/manifests/base | kubectl apply -f -
# set paths for locally managed ssh known hosts and tls certs data
export ARGOCD_SSH_DATA_PATH=/tmp/argo-e2e/app/config/ssh
export ARGOCD_TLS_DATA_PATH=/tmp/argo-e2e/app/config/tls
goreman start
# Cleans VSCode debug.test files from sub-dirs to prevent them from being included in packr boxes

View file

@ -3282,6 +3282,14 @@
"type": "string",
"title": "SSH private key data for authenticating at the repo server"
},
"tlsClientCertData": {
"type": "string",
"title": "TLS client cert data for authenticating at the repo server"
},
"tlsClientCertKey": {
"type": "string",
"title": "TLS client cert key for authenticating at the repo server"
},
"username": {
"type": "string",
"title": "Username for authenticating at the repo server"

View file

@ -16,13 +16,14 @@ import (
appsv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/cli"
"github.com/argoproj/argo-cd/util/git"
)
// NewRepoCommand returns a new instance of an `argocd repo` command
func NewRepoCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var command = &cobra.Command{
Use: "repo",
Short: "Manage git repository credentials",
Short: "Manage git repository connection parameters",
Run: func(c *cobra.Command, args []string) {
c.HelpFunc()(c, args)
os.Exit(1)
@ -43,24 +44,70 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
sshPrivateKeyPath string
insecureIgnoreHostKey bool
insecureSkipServerVerification bool
tlsClientCertPath string
tlsClientCertKeyPath string
enableLfs bool
)
// For better readability and easier formatting
var repoAddExamples = `
Add a SSH repository using a private key for authentication, ignoring the server's host key:",
$ argocd repo add git@git.example.com --insecure-ignore-host-key --ssh-private-key-path ~/id_rsa",
Add a HTTPS repository using username/password and TLS client certificates:",
$ argocd repo add https://git.example.com --username git --password secret --tls-client-cert-path ~/mycert.crt --tls-client-cert-key-path ~/mycert.key",
Add a HTTPS repository using username/password without verifying the server's TLS certificate:",
$ argocd repo add https://git.example.com --username git --password secret --insecure-skip-server-verification",
`
var command = &cobra.Command{
Use: "add REPO",
Short: "Add git repository credentials",
Use: "add REPOURL",
Short: "Add git repository connection parameters",
Example: repoAddExamples,
Run: func(c *cobra.Command, args []string) {
if len(args) != 1 {
c.HelpFunc()(c, args)
os.Exit(1)
}
// Repository URL
repo.Repo = args[0]
// Specifying ssh-private-key-path is only valid for SSH repositories
if sshPrivateKeyPath != "" {
keyData, err := ioutil.ReadFile(sshPrivateKeyPath)
if err != nil {
log.Fatal(err)
if ok, _ := git.IsSSHURL(repo.Repo); ok {
keyData, err := ioutil.ReadFile(sshPrivateKeyPath)
if err != nil {
log.Fatal(err)
}
repo.SSHPrivateKey = string(keyData)
} else {
err := fmt.Errorf("--ssh-private-key-path is only supported for SSH repositories.")
errors.CheckError(err)
}
repo.SSHPrivateKey = string(keyData)
}
// tls-client-cert-path and tls-client-cert-key-key-path must always be
// specified together
if (tlsClientCertPath != "" && tlsClientCertKeyPath == "") || (tlsClientCertPath == "" && tlsClientCertKeyPath != "") {
err := fmt.Errorf("--tls-client-cert-path and --tls-client-cert-key-path must be specified together")
errors.CheckError(err)
}
// Specifying tls-client-cert-path is only valid for HTTPS repositories
if tlsClientCertPath != "" {
if git.IsHTTPSURL(repo.Repo) {
tlsCertData, err := ioutil.ReadFile(tlsClientCertPath)
errors.CheckError(err)
tlsCertKey, err := ioutil.ReadFile(tlsClientCertKeyPath)
errors.CheckError(err)
repo.TLSClientCertData = string(tlsCertData)
repo.TLSClientCertKey = string(tlsCertKey)
} else {
err := fmt.Errorf("--tls-client-cert-path is only supported for HTTPS repositories")
errors.CheckError(err)
}
}
// InsecureIgnoreHostKey is deprecated and only here for backwards compat
repo.InsecureIgnoreHostKey = insecureIgnoreHostKey
repo.Insecure = insecureSkipServerVerification
@ -79,11 +126,13 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
// it is a private repo, but we cannot access with with the credentials
// that were supplied, we bail out.
repoAccessReq := repositorypkg.RepoAccessQuery{
Repo: repo.Repo,
Username: repo.Username,
Password: repo.Password,
SshPrivateKey: repo.SSHPrivateKey,
Insecure: repo.IsInsecure(),
Repo: repo.Repo,
Username: repo.Username,
Password: repo.Password,
SshPrivateKey: repo.SSHPrivateKey,
TlsClientCertData: repo.TLSClientCertData,
TlsClientCertKey: repo.TLSClientCertKey,
Insecure: repo.IsInsecure(),
}
_, err := repoIf.ValidateAccess(context.Background(), &repoAccessReq)
errors.CheckError(err)
@ -100,6 +149,8 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
command.Flags().StringVar(&repo.Username, "username", "", "username to the repository")
command.Flags().StringVar(&repo.Password, "password", "", "password to the repository")
command.Flags().StringVar(&sshPrivateKeyPath, "ssh-private-key-path", "", "path to the private ssh key (e.g. ~/.ssh/id_rsa)")
command.Flags().StringVar(&tlsClientCertPath, "tls-client-cert-path", "", "path to the TLS client cert (must be PEM format)")
command.Flags().StringVar(&tlsClientCertKeyPath, "tls-client-cert-key-path", "", "path to the TLS client cert's key path (must be PEM format)")
command.Flags().BoolVar(&insecureIgnoreHostKey, "insecure-ignore-host-key", false, "disables SSH strict host key checking (deprecated, use --insecure-skip-server-validation instead)")
command.Flags().BoolVar(&insecureSkipServerVerification, "insecure-skip-server-verification", false, "disables server certificate and host key checks")
command.Flags().BoolVar(&enableLfs, "enable-lfs", false, "enable git-lfs (Large File Support) on this repository")

View file

@ -35,6 +35,18 @@ const (
DefaultPortRepoServerMetrics = 8084
)
// Default paths on the pod's file system
const (
// The default base path where application config is located
DefaultPathAppConfig = "/app/config"
// The default path where TLS certificates for repositories are located
DefaultPathTLSConfig = "/app/config/tls"
// The default path where SSH known hosts are stored
DefaultPathSSHConfig = "/app/config/ssh"
// Default name for the SSH known hosts file
DefaultSSHKnownHostsName = "ssh_known_hosts"
)
// Argo CD application related constants
const (
// KubernetesInternalAPIServerAddr is address of the k8s API server when accessing internal to the cluster
@ -119,6 +131,10 @@ const (
// EnvVarFakeInClusterConfig is an environment variable to fake an in-cluster RESTConfig using
// the current kubectl context (for development purposes)
EnvVarFakeInClusterConfig = "ARGOCD_FAKE_IN_CLUSTER"
// Overrides the location where SSH known hosts for repo access data is stored
EnvVarSSHDataPath = "ARGOCD_SSH_DATA_PATH"
// Overrides the location where TLS certificate for repo access data is stored
EnvVarTLSDataPath = "ARGOCD_TLS_DATA_PATH"
)
const (

View file

@ -31,6 +31,21 @@ Instead of using username and password you might use access token. Following ins
Then, connect the repository using an empty string as a username and access token value as a password.
### TLS Client Certificates for HTTPS repositories
> v1.3 and later
If your repository server requires you to use TLS client certificates for authentication, you can configure ArgoCD repositories to make use of them. For this purpose, `--tls-client-cert-path` and `--tls-client-cert-key-path` switches to the `argocd repo add` command can be used to specify the files on your local system containing client certificate and the corresponding key, respectively:
```
argocd repo add https://repo.example.com/repo.git --tls-client-cert-path ~/mycert.crt --tls-client-cert-key-path ~/mycert.key
```
Of course, you can also use this in combination with the `--username` and `--password` switches, if your repository server should require this. The options `--tls-client-cert-path` and `--tls-client-cert-key-path` must always be specified together.
!!! note
Your client certificate and key data must be in PEM format, other formats (such as PKCS12) are not understood. Also make sure that your certificate's key is not password protected, otherwise it cannot be used by ArgoCD.
### SSH Private Key Credential
Private repositories that require an SSH private key have a URL that typically start with "git@" or "ssh://" rather than "https://".

View file

@ -47,7 +47,7 @@ func (m *RepoAppsQuery) Reset() { *m = RepoAppsQuery{} }
func (m *RepoAppsQuery) String() string { return proto.CompactTextString(m) }
func (*RepoAppsQuery) ProtoMessage() {}
func (*RepoAppsQuery) Descriptor() ([]byte, []int) {
return fileDescriptor_repository_fd679778f6b4cef4, []int{0}
return fileDescriptor_repository_3b37238e26fe0d6f, []int{0}
}
func (m *RepoAppsQuery) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -103,7 +103,7 @@ func (m *AppInfo) Reset() { *m = AppInfo{} }
func (m *AppInfo) String() string { return proto.CompactTextString(m) }
func (*AppInfo) ProtoMessage() {}
func (*AppInfo) Descriptor() ([]byte, []int) {
return fileDescriptor_repository_fd679778f6b4cef4, []int{1}
return fileDescriptor_repository_3b37238e26fe0d6f, []int{1}
}
func (m *AppInfo) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -162,7 +162,7 @@ func (m *RepoAppDetailsQuery) Reset() { *m = RepoAppDetailsQuery{} }
func (m *RepoAppDetailsQuery) String() string { return proto.CompactTextString(m) }
func (*RepoAppDetailsQuery) ProtoMessage() {}
func (*RepoAppDetailsQuery) Descriptor() ([]byte, []int) {
return fileDescriptor_repository_fd679778f6b4cef4, []int{2}
return fileDescriptor_repository_3b37238e26fe0d6f, []int{2}
}
func (m *RepoAppDetailsQuery) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -238,7 +238,7 @@ func (m *RepoAppsResponse) Reset() { *m = RepoAppsResponse{} }
func (m *RepoAppsResponse) String() string { return proto.CompactTextString(m) }
func (*RepoAppsResponse) ProtoMessage() {}
func (*RepoAppsResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_repository_fd679778f6b4cef4, []int{3}
return fileDescriptor_repository_3b37238e26fe0d6f, []int{3}
}
func (m *RepoAppsResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -286,7 +286,7 @@ func (m *RepoQuery) Reset() { *m = RepoQuery{} }
func (m *RepoQuery) String() string { return proto.CompactTextString(m) }
func (*RepoQuery) ProtoMessage() {}
func (*RepoQuery) Descriptor() ([]byte, []int) {
return fileDescriptor_repository_fd679778f6b4cef4, []int{4}
return fileDescriptor_repository_3b37238e26fe0d6f, []int{4}
}
func (m *RepoQuery) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -333,7 +333,11 @@ type RepoAccessQuery struct {
// Private key data for accessing SSH repository
SshPrivateKey string `protobuf:"bytes,4,opt,name=sshPrivateKey,proto3" json:"sshPrivateKey,omitempty"`
// Whether to skip certificate or host key validation
Insecure bool `protobuf:"varint,5,opt,name=insecure,proto3" json:"insecure,omitempty"`
Insecure bool `protobuf:"varint,5,opt,name=insecure,proto3" json:"insecure,omitempty"`
// TLS client cert data for accessing HTTPS repository
TlsClientCertData string `protobuf:"bytes,6,opt,name=tlsClientCertData,proto3" json:"tlsClientCertData,omitempty"`
// TLS client cert key for accessing HTTPS repository
TlsClientCertKey string `protobuf:"bytes,7,opt,name=tlsClientCertKey,proto3" json:"tlsClientCertKey,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -343,7 +347,7 @@ func (m *RepoAccessQuery) Reset() { *m = RepoAccessQuery{} }
func (m *RepoAccessQuery) String() string { return proto.CompactTextString(m) }
func (*RepoAccessQuery) ProtoMessage() {}
func (*RepoAccessQuery) Descriptor() ([]byte, []int) {
return fileDescriptor_repository_fd679778f6b4cef4, []int{5}
return fileDescriptor_repository_3b37238e26fe0d6f, []int{5}
}
func (m *RepoAccessQuery) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -407,6 +411,20 @@ func (m *RepoAccessQuery) GetInsecure() bool {
return false
}
func (m *RepoAccessQuery) GetTlsClientCertData() string {
if m != nil {
return m.TlsClientCertData
}
return ""
}
func (m *RepoAccessQuery) GetTlsClientCertKey() string {
if m != nil {
return m.TlsClientCertKey
}
return ""
}
type RepoResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
@ -417,7 +435,7 @@ func (m *RepoResponse) Reset() { *m = RepoResponse{} }
func (m *RepoResponse) String() string { return proto.CompactTextString(m) }
func (*RepoResponse) ProtoMessage() {}
func (*RepoResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_repository_fd679778f6b4cef4, []int{6}
return fileDescriptor_repository_3b37238e26fe0d6f, []int{6}
}
func (m *RepoResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -458,7 +476,7 @@ func (m *RepoCreateRequest) Reset() { *m = RepoCreateRequest{} }
func (m *RepoCreateRequest) String() string { return proto.CompactTextString(m) }
func (*RepoCreateRequest) ProtoMessage() {}
func (*RepoCreateRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_repository_fd679778f6b4cef4, []int{7}
return fileDescriptor_repository_3b37238e26fe0d6f, []int{7}
}
func (m *RepoCreateRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -512,7 +530,7 @@ func (m *RepoUpdateRequest) Reset() { *m = RepoUpdateRequest{} }
func (m *RepoUpdateRequest) String() string { return proto.CompactTextString(m) }
func (*RepoUpdateRequest) ProtoMessage() {}
func (*RepoUpdateRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_repository_fd679778f6b4cef4, []int{8}
return fileDescriptor_repository_3b37238e26fe0d6f, []int{8}
}
func (m *RepoUpdateRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -1078,6 +1096,18 @@ func (m *RepoAccessQuery) MarshalTo(dAtA []byte) (int, error) {
}
i++
}
if len(m.TlsClientCertData) > 0 {
dAtA[i] = 0x32
i++
i = encodeVarintRepository(dAtA, i, uint64(len(m.TlsClientCertData)))
i += copy(dAtA[i:], m.TlsClientCertData)
}
if len(m.TlsClientCertKey) > 0 {
dAtA[i] = 0x3a
i++
i = encodeVarintRepository(dAtA, i, uint64(len(m.TlsClientCertKey)))
i += copy(dAtA[i:], m.TlsClientCertKey)
}
if m.XXX_unrecognized != nil {
i += copy(dAtA[i:], m.XXX_unrecognized)
}
@ -1299,6 +1329,14 @@ func (m *RepoAccessQuery) Size() (n int) {
if m.Insecure {
n += 2
}
l = len(m.TlsClientCertData)
if l > 0 {
n += 1 + l + sovRepository(uint64(l))
}
l = len(m.TlsClientCertKey)
if l > 0 {
n += 1 + l + sovRepository(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@ -2105,6 +2143,64 @@ func (m *RepoAccessQuery) Unmarshal(dAtA []byte) error {
}
}
m.Insecure = bool(v != 0)
case 6:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field TlsClientCertData", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowRepository
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthRepository
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.TlsClientCertData = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 7:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field TlsClientCertKey", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowRepository
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthRepository
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.TlsClientCertKey = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipRepository(dAtA[iNdEx:])
@ -2472,58 +2568,60 @@ var (
)
func init() {
proto.RegisterFile("server/repository/repository.proto", fileDescriptor_repository_fd679778f6b4cef4)
proto.RegisterFile("server/repository/repository.proto", fileDescriptor_repository_3b37238e26fe0d6f)
}
var fileDescriptor_repository_fd679778f6b4cef4 = []byte{
// 784 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x55, 0x4f, 0x6f, 0x1b, 0x45,
0x14, 0xd7, 0x26, 0x8e, 0xe3, 0x4c, 0x48, 0x80, 0x49, 0x88, 0xcc, 0xc6, 0x49, 0xac, 0x21, 0x12,
0x0e, 0x82, 0x5d, 0xd9, 0xe1, 0x80, 0x10, 0x08, 0x25, 0x04, 0x41, 0x14, 0x0e, 0xb0, 0x08, 0x24,
0x38, 0x80, 0x36, 0xeb, 0xc7, 0x7a, 0xf0, 0x7a, 0x67, 0x98, 0x19, 0x2f, 0xb2, 0xa2, 0x5c, 0x90,
0x9a, 0x0f, 0xd0, 0xde, 0xdb, 0x5b, 0x3f, 0x4b, 0x0f, 0x3d, 0x54, 0xea, 0x17, 0xa8, 0xa2, 0x7e,
0x90, 0x6a, 0x66, 0xff, 0x78, 0xe3, 0x38, 0x4e, 0x55, 0x45, 0xbd, 0xbd, 0x79, 0xf3, 0xde, 0xfb,
0xfd, 0xde, 0x9f, 0x79, 0x83, 0x88, 0x04, 0x91, 0x80, 0x70, 0x05, 0x70, 0x26, 0xa9, 0x62, 0x62,
0x54, 0x12, 0x1d, 0x2e, 0x98, 0x62, 0x18, 0x8d, 0x35, 0xf6, 0x7a, 0xc8, 0x42, 0x66, 0xd4, 0xae,
0x96, 0x52, 0x0b, 0xbb, 0x11, 0x32, 0x16, 0x46, 0xe0, 0xfa, 0x9c, 0xba, 0x7e, 0x1c, 0x33, 0xe5,
0x2b, 0xca, 0x62, 0x99, 0xdd, 0x92, 0xfe, 0x17, 0xd2, 0xa1, 0xcc, 0xdc, 0x06, 0x4c, 0x80, 0x9b,
0xb4, 0xdd, 0x10, 0x62, 0x10, 0xbe, 0x82, 0x6e, 0x66, 0x73, 0x1c, 0x52, 0xd5, 0x1b, 0x9e, 0x3a,
0x01, 0x1b, 0xb8, 0xbe, 0x30, 0x10, 0xff, 0x18, 0xe1, 0xb3, 0xa0, 0xeb, 0xf2, 0x7e, 0xa8, 0x9d,
0xa5, 0xeb, 0x73, 0x1e, 0xd1, 0xc0, 0x04, 0x77, 0x93, 0xb6, 0x1f, 0xf1, 0x9e, 0x7f, 0x3d, 0xd4,
0xe1, 0xac, 0x50, 0x26, 0x95, 0x5b, 0x53, 0x26, 0xdf, 0xa0, 0x15, 0x0f, 0x38, 0x3b, 0xe0, 0x5c,
0xfe, 0x3c, 0x04, 0x31, 0xc2, 0x18, 0x55, 0xb4, 0x51, 0xdd, 0x6a, 0x5a, 0xad, 0x25, 0xcf, 0xc8,
0xd8, 0x46, 0x35, 0x01, 0x09, 0x95, 0x94, 0xc5, 0xf5, 0x39, 0xa3, 0x2f, 0xce, 0xa4, 0x8d, 0x16,
0x0f, 0x38, 0x3f, 0x8e, 0xff, 0x66, 0xda, 0x55, 0x8d, 0x38, 0xe4, 0xae, 0x5a, 0xd6, 0x3a, 0xee,
0xab, 0x5e, 0xe6, 0x66, 0x64, 0xf2, 0xd4, 0x42, 0x6b, 0x19, 0xe8, 0x11, 0x28, 0x9f, 0x46, 0x6f,
0x06, 0x5d, 0xc4, 0x9e, 0x1f, 0xc7, 0xc6, 0xfb, 0xa8, 0xd2, 0x83, 0x68, 0x50, 0xaf, 0x34, 0xad,
0xd6, 0x72, 0x67, 0xc7, 0x29, 0x25, 0xfc, 0x03, 0x44, 0x83, 0x09, 0x48, 0xcf, 0x18, 0xe3, 0xaf,
0xd0, 0x62, 0x5f, 0xb2, 0x38, 0x06, 0x55, 0x5f, 0x30, 0x7e, 0xa4, 0xec, 0x77, 0x92, 0x5e, 0x4d,
0xba, 0xe6, 0x2e, 0xe4, 0x6b, 0xf4, 0x5e, 0x5e, 0x42, 0x0f, 0x24, 0x67, 0xb1, 0x04, 0xbc, 0x87,
0x16, 0xa8, 0x82, 0x81, 0xac, 0x5b, 0xcd, 0xf9, 0xd6, 0x72, 0x67, 0xad, 0x1c, 0x2f, 0x2b, 0x97,
0x97, 0x5a, 0x90, 0x1d, 0xb4, 0xa4, 0xdd, 0x6f, 0x2c, 0x01, 0x79, 0x64, 0xa1, 0x77, 0x0d, 0x40,
0x10, 0x80, 0x9c, 0x5d, 0xaa, 0xa1, 0x04, 0x11, 0xfb, 0x03, 0xc8, 0x4b, 0x95, 0x9f, 0xf5, 0x1d,
0xf7, 0xa5, 0xfc, 0x8f, 0x89, 0x6e, 0x56, 0xae, 0xe2, 0x8c, 0x77, 0xd1, 0x8a, 0x94, 0xbd, 0x9f,
0x04, 0x4d, 0x7c, 0x05, 0x27, 0x30, 0x32, 0xb5, 0x5b, 0xf2, 0xae, 0x2a, 0x75, 0x04, 0x1a, 0x4b,
0x08, 0x86, 0x02, 0x4c, 0x91, 0x6a, 0x5e, 0x71, 0x26, 0xab, 0xe8, 0x1d, 0x4d, 0x30, 0xcf, 0x9e,
0x5c, 0x58, 0xe8, 0x7d, 0xad, 0xf8, 0x56, 0x80, 0xaf, 0xc0, 0x83, 0x7f, 0x87, 0x20, 0x15, 0xfe,
0xbd, 0xc4, 0x79, 0xb9, 0xf3, 0x9d, 0x33, 0x9e, 0x5e, 0x27, 0x9f, 0x5e, 0x23, 0xfc, 0x15, 0x74,
0x1d, 0xde, 0x0f, 0x1d, 0xfd, 0x10, 0x9c, 0xd2, 0x43, 0x70, 0xf2, 0x87, 0xe0, 0x78, 0x45, 0x31,
0xb3, 0xd4, 0x37, 0x50, 0x75, 0xc8, 0x25, 0x08, 0x65, 0x12, 0xaf, 0x79, 0xd9, 0x89, 0xc4, 0x29,
0x8f, 0x5f, 0x79, 0xf7, 0xad, 0xf0, 0xe8, 0x3c, 0x5e, 0x4c, 0x01, 0x53, 0xe5, 0x2f, 0x20, 0x12,
0x1a, 0x00, 0xbe, 0xb0, 0x50, 0xe5, 0x47, 0x2a, 0x15, 0xfe, 0xa0, 0x3c, 0x06, 0x45, 0xd3, 0xed,
0xe3, 0x3b, 0xa1, 0xa0, 0x11, 0x48, 0xe3, 0xff, 0xe7, 0x2f, 0x1f, 0xcc, 0x6d, 0xe0, 0x75, 0xb3,
0x83, 0x92, 0xf6, 0xf8, 0xc1, 0x53, 0x90, 0x78, 0x80, 0x6a, 0xda, 0x4a, 0x4f, 0x2a, 0xfe, 0x70,
0x92, 0x4b, 0xb1, 0x02, 0xec, 0xc6, 0xb4, 0xab, 0xa2, 0xb9, 0x2d, 0x03, 0x41, 0x70, 0x73, 0x1a,
0x84, 0x7b, 0xa6, 0x4f, 0xe7, 0x7a, 0x7f, 0x49, 0x7c, 0xcf, 0x42, 0x2b, 0xdf, 0x97, 0x1f, 0x0e,
0xde, 0x99, 0x12, 0xb9, 0xfc, 0xa8, 0x6c, 0x72, 0xb3, 0x41, 0x41, 0xc0, 0x35, 0x04, 0xf6, 0xf0,
0xc7, 0xb7, 0x11, 0x70, 0xcf, 0xf4, 0x4a, 0x38, 0xc7, 0xf7, 0x2d, 0x54, 0x4d, 0x47, 0x11, 0x6f,
0x4d, 0xc6, 0xbf, 0x32, 0xa2, 0xf6, 0xdd, 0x0c, 0x03, 0x21, 0x86, 0x61, 0x83, 0x4c, 0xed, 0xc2,
0x97, 0xe9, 0xc8, 0x3e, 0xb4, 0x50, 0x35, 0x9d, 0xcb, 0xeb, 0xa4, 0xae, 0xcc, 0xeb, 0x5d, 0x91,
0x72, 0x0c, 0xa9, 0x96, 0x3d, 0xa3, 0x6f, 0x86, 0xc7, 0x79, 0x46, 0xf0, 0x4f, 0x54, 0x3d, 0x82,
0x08, 0x14, 0xdc, 0x34, 0xb6, 0xf5, 0x49, 0x75, 0xd1, 0xa1, 0x8f, 0x0c, 0xd4, 0xd6, 0x27, 0x9b,
0x33, 0x3a, 0x84, 0xcf, 0xd0, 0xea, 0x6f, 0x7e, 0x44, 0x75, 0xa6, 0xe9, 0x66, 0xc3, 0x9b, 0xd7,
0x9a, 0x3f, 0xde, 0x78, 0x33, 0xd0, 0x3a, 0x06, 0xed, 0x53, 0xb2, 0x3b, 0x6b, 0x1e, 0x92, 0x0c,
0x2a, 0x4d, 0xee, 0xf0, 0xf0, 0xc9, 0xe5, 0xb6, 0xf5, 0xec, 0x72, 0xdb, 0x7a, 0x71, 0xb9, 0x6d,
0xfd, 0xf1, 0xf9, 0x6b, 0xfc, 0xc9, 0x41, 0x44, 0x21, 0x56, 0xa5, 0x0f, 0xf4, 0xb4, 0x6a, 0x7e,
0xd0, 0xfd, 0x57, 0x01, 0x00, 0x00, 0xff, 0xff, 0x44, 0x05, 0xf6, 0xec, 0x5a, 0x08, 0x00, 0x00,
var fileDescriptor_repository_3b37238e26fe0d6f = []byte{
// 815 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x55, 0xcd, 0x6e, 0x23, 0x45,
0x10, 0xd6, 0xec, 0x7a, 0x6d, 0xa7, 0x43, 0x96, 0xdd, 0xde, 0x65, 0x65, 0x66, 0xbd, 0x89, 0xd5,
0xac, 0x84, 0x77, 0x15, 0x66, 0x64, 0x87, 0x03, 0x42, 0x20, 0x94, 0x1f, 0x04, 0x51, 0x38, 0xc0,
0x20, 0x90, 0xe0, 0x00, 0xea, 0x8c, 0x8b, 0x71, 0xe3, 0xf1, 0x74, 0xd3, 0xdd, 0x1e, 0x64, 0x45,
0xb9, 0x20, 0x91, 0x07, 0x80, 0x3b, 0x47, 0x9e, 0x85, 0x03, 0x07, 0x24, 0x5e, 0x00, 0x45, 0xbc,
0x04, 0x37, 0xd4, 0x3d, 0x3f, 0x1e, 0xff, 0xc4, 0x41, 0x28, 0xda, 0x5b, 0x75, 0x75, 0x55, 0x7d,
0x5f, 0xd7, 0x5f, 0x23, 0xa2, 0x40, 0xa6, 0x20, 0x7d, 0x09, 0x82, 0x2b, 0xa6, 0xb9, 0x9c, 0x56,
0x44, 0x4f, 0x48, 0xae, 0x39, 0x46, 0x33, 0x8d, 0xfb, 0x30, 0xe2, 0x11, 0xb7, 0x6a, 0xdf, 0x48,
0x99, 0x85, 0xdb, 0x8e, 0x38, 0x8f, 0x62, 0xf0, 0xa9, 0x60, 0x3e, 0x4d, 0x12, 0xae, 0xa9, 0x66,
0x3c, 0x51, 0xf9, 0x2d, 0x19, 0xbd, 0xa5, 0x3c, 0xc6, 0xed, 0x6d, 0xc8, 0x25, 0xf8, 0x69, 0xcf,
0x8f, 0x20, 0x01, 0x49, 0x35, 0x0c, 0x72, 0x9b, 0xe3, 0x88, 0xe9, 0xe1, 0xe4, 0xd4, 0x0b, 0xf9,
0xd8, 0xa7, 0xd2, 0x42, 0x7c, 0x6b, 0x85, 0x37, 0xc2, 0x81, 0x2f, 0x46, 0x91, 0x71, 0x56, 0x3e,
0x15, 0x22, 0x66, 0xa1, 0x0d, 0xee, 0xa7, 0x3d, 0x1a, 0x8b, 0x21, 0x5d, 0x0e, 0x75, 0xb0, 0x2e,
0x94, 0x7d, 0xca, 0xb5, 0x4f, 0x26, 0xef, 0xa1, 0xad, 0x00, 0x04, 0xdf, 0x17, 0x42, 0x7d, 0x32,
0x01, 0x39, 0xc5, 0x18, 0xd5, 0x8c, 0x51, 0xcb, 0xe9, 0x38, 0xdd, 0x8d, 0xc0, 0xca, 0xd8, 0x45,
0x4d, 0x09, 0x29, 0x53, 0x8c, 0x27, 0xad, 0x5b, 0x56, 0x5f, 0x9e, 0x49, 0x0f, 0x35, 0xf6, 0x85,
0x38, 0x4e, 0xbe, 0xe1, 0xc6, 0x55, 0x4f, 0x05, 0x14, 0xae, 0x46, 0x36, 0x3a, 0x41, 0xf5, 0x30,
0x77, 0xb3, 0x32, 0xf9, 0xdd, 0x41, 0x0f, 0x72, 0xd0, 0x23, 0xd0, 0x94, 0xc5, 0xff, 0x0f, 0xba,
0x8c, 0x7d, 0x7b, 0x16, 0x1b, 0xef, 0xa1, 0xda, 0x10, 0xe2, 0x71, 0xab, 0xd6, 0x71, 0xba, 0x9b,
0xfd, 0x1d, 0xaf, 0xf2, 0xe0, 0x0f, 0x21, 0x1e, 0x2f, 0x40, 0x06, 0xd6, 0x18, 0xbf, 0x83, 0x1a,
0x23, 0xc5, 0x93, 0x04, 0x74, 0xeb, 0x8e, 0xf5, 0x23, 0x55, 0xbf, 0x93, 0xec, 0x6a, 0xd1, 0xb5,
0x70, 0x21, 0xef, 0xa2, 0x7b, 0x45, 0x0a, 0x03, 0x50, 0x82, 0x27, 0x0a, 0xf0, 0x33, 0x74, 0x87,
0x69, 0x18, 0xab, 0x96, 0xd3, 0xb9, 0xdd, 0xdd, 0xec, 0x3f, 0xa8, 0xc6, 0xcb, 0xd3, 0x15, 0x64,
0x16, 0x64, 0x07, 0x6d, 0x18, 0xf7, 0x2b, 0x53, 0x40, 0xfe, 0x71, 0xd0, 0xcb, 0x16, 0x20, 0x0c,
0x41, 0xad, 0x4f, 0xd5, 0x44, 0x81, 0x4c, 0xe8, 0x18, 0x8a, 0x54, 0x15, 0x67, 0x73, 0x27, 0xa8,
0x52, 0xdf, 0x73, 0x39, 0xc8, 0xd3, 0x55, 0x9e, 0xf1, 0x53, 0xb4, 0xa5, 0xd4, 0xf0, 0x63, 0xc9,
0x52, 0xaa, 0xe1, 0x04, 0xa6, 0x36, 0x77, 0x1b, 0xc1, 0xbc, 0xd2, 0x44, 0x60, 0x89, 0x82, 0x70,
0x22, 0xc1, 0x26, 0xa9, 0x19, 0x94, 0x67, 0xbc, 0x8b, 0xee, 0xeb, 0x58, 0x1d, 0xc6, 0x0c, 0x12,
0x7d, 0x08, 0x52, 0x1f, 0x51, 0x4d, 0x5b, 0x75, 0x1b, 0x65, 0xf9, 0x02, 0x3f, 0x47, 0xf7, 0xe6,
0x94, 0x06, 0xb2, 0x61, 0x8d, 0x97, 0xf4, 0xe4, 0x2e, 0x7a, 0xc9, 0x3c, 0xbd, 0xc8, 0x2b, 0xb9,
0x70, 0xd0, 0x7d, 0xa3, 0x38, 0x94, 0x40, 0x35, 0x04, 0xf0, 0xdd, 0x04, 0x94, 0xc6, 0x5f, 0x54,
0xb2, 0xb1, 0xd9, 0x7f, 0xdf, 0x9b, 0xcd, 0x85, 0x57, 0xcc, 0x85, 0x15, 0xbe, 0x0e, 0x07, 0x9e,
0x18, 0x45, 0x9e, 0x19, 0x31, 0xaf, 0x32, 0x62, 0x5e, 0x31, 0x62, 0x5e, 0x50, 0x96, 0x29, 0x4f,
0xea, 0x23, 0x54, 0x9f, 0x08, 0x05, 0x52, 0xdb, 0x94, 0x36, 0x83, 0xfc, 0x44, 0x92, 0x8c, 0xc7,
0x67, 0x62, 0xf0, 0x42, 0x78, 0xf4, 0x7f, 0x6d, 0x64, 0x80, 0x99, 0xf2, 0x53, 0x90, 0x29, 0x0b,
0x01, 0x5f, 0x38, 0xa8, 0xf6, 0x11, 0x53, 0x1a, 0xbf, 0x52, 0x6d, 0xb0, 0xb2, 0x9d, 0xdc, 0xe3,
0x1b, 0xa1, 0x60, 0x10, 0x48, 0xfb, 0x87, 0x3f, 0xff, 0xfe, 0xf9, 0xd6, 0x23, 0xfc, 0xd0, 0x6e,
0xb7, 0xb4, 0x37, 0x5b, 0x25, 0x0c, 0x14, 0x1e, 0xa3, 0xa6, 0xb1, 0x32, 0x33, 0x80, 0x5f, 0x5d,
0xe4, 0x52, 0x2e, 0x17, 0xb7, 0xbd, 0xea, 0xaa, 0x2c, 0x6e, 0xd7, 0x42, 0x10, 0xdc, 0x59, 0x05,
0xe1, 0x9f, 0x99, 0xd3, 0xb9, 0xd9, 0x8c, 0x0a, 0xff, 0xe8, 0xa0, 0xad, 0x0f, 0xaa, 0x23, 0x89,
0x77, 0x56, 0x44, 0xae, 0x8e, 0xab, 0x4b, 0xae, 0x36, 0x28, 0x09, 0xf8, 0x96, 0xc0, 0x33, 0xfc,
0xfa, 0x75, 0x04, 0xfc, 0x33, 0xb3, 0x6c, 0xce, 0xf1, 0x4f, 0x0e, 0xaa, 0x67, 0xad, 0x88, 0x9f,
0x2c, 0xc6, 0x9f, 0x6b, 0x51, 0xf7, 0x66, 0x9a, 0x81, 0x10, 0xcb, 0xb0, 0x4d, 0x56, 0x56, 0xe1,
0xed, 0xac, 0x65, 0x7f, 0x71, 0x50, 0x3d, 0xeb, 0xcb, 0x65, 0x52, 0x73, 0xfd, 0x7a, 0x53, 0xa4,
0x3c, 0x4b, 0xaa, 0xeb, 0xae, 0xa9, 0x9b, 0xe5, 0x71, 0x9e, 0x13, 0xfc, 0x0a, 0xd5, 0x8f, 0x20,
0x06, 0x0d, 0x57, 0xb5, 0x6d, 0x6b, 0x51, 0x5d, 0x56, 0xe8, 0x35, 0x0b, 0xf5, 0xe4, 0xf9, 0xe3,
0x35, 0x15, 0xc2, 0x67, 0xe8, 0xee, 0xe7, 0x34, 0x66, 0xe6, 0xa5, 0xd9, 0xce, 0xc4, 0x8f, 0x97,
0x8a, 0x3f, 0xdb, 0xa5, 0x6b, 0xd0, 0xfa, 0x16, 0x6d, 0x97, 0x3c, 0x5d, 0xd7, 0x0f, 0x69, 0x0e,
0x95, 0x3d, 0xee, 0xe0, 0xe0, 0xb7, 0xcb, 0x6d, 0xe7, 0x8f, 0xcb, 0x6d, 0xe7, 0xaf, 0xcb, 0x6d,
0xe7, 0xcb, 0x37, 0xff, 0xc3, 0x6f, 0x1f, 0xda, 0x8d, 0x57, 0xf9, 0x9a, 0x4f, 0xeb, 0xf6, 0x6f,
0xde, 0xfb, 0x37, 0x00, 0x00, 0xff, 0xff, 0x6c, 0xda, 0x10, 0xbf, 0xb4, 0x08, 0x00, 0x00,
}

File diff suppressed because it is too large Load diff

View file

@ -496,6 +496,7 @@ message Repository {
// SSH private key data for authenticating at the repo server
optional string sshPrivateKey = 4;
// Current state of repository server connecting
optional ConnectionState connectionState = 5;
// InsecureIgnoreHostKey should not be used anymore, Insecure is favoured
@ -506,6 +507,12 @@ message Repository {
// Whether git-lfs support should be enabled for this repo
optional bool enableLfs = 8;
// TLS client cert data for authenticating at the repo server
optional string tlsClientCertData = 9;
// TLS client cert key for authenticating at the repo server
optional string tlsClientCertKey = 10;
}
// A RepositoryCertificate is either SSH known hosts entry or TLS certificate

View file

@ -1779,7 +1779,8 @@ func schema_pkg_apis_application_v1alpha1_Repository(ref common.ReferenceCallbac
},
"connectionState": {
SchemaProps: spec.SchemaProps{
Ref: ref("github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ConnectionState"),
Description: "Current state of repository server connecting",
Ref: ref("github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1.ConnectionState"),
},
},
"insecureIgnoreHostKey": {
@ -1803,6 +1804,20 @@ func schema_pkg_apis_application_v1alpha1_Repository(ref common.ReferenceCallbac
Format: "",
},
},
"tlsClientCertData": {
SchemaProps: spec.SchemaProps{
Description: "TLS client cert data for authenticating at the repo server",
Type: []string{"string"},
Format: "",
},
},
"tlsClientCertKey": {
SchemaProps: spec.SchemaProps{
Description: "TLS client cert key for authenticating at the repo server",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"repo"},
},

View file

@ -889,7 +889,8 @@ type Repository struct {
// Password for authenticating at the repo server
Password string `json:"password,omitempty" protobuf:"bytes,3,opt,name=password"`
// SSH private key data for authenticating at the repo server
SSHPrivateKey string `json:"sshPrivateKey,omitempty" protobuf:"bytes,4,opt,name=sshPrivateKey"`
SSHPrivateKey string `json:"sshPrivateKey,omitempty" protobuf:"bytes,4,opt,name=sshPrivateKey"`
// Current state of repository server connecting
ConnectionState ConnectionState `json:"connectionState,omitempty" protobuf:"bytes,5,opt,name=connectionState"`
// InsecureIgnoreHostKey should not be used anymore, Insecure is favoured
InsecureIgnoreHostKey bool `json:"insecureIgnoreHostKey,omitempty" protobuf:"bytes,6,opt,name=insecureIgnoreHostKey"`
@ -897,6 +898,10 @@ type Repository struct {
Insecure bool `json:"insecure,omitempty" protobuf:"bytes,7,opt,name=insecure"`
// Whether git-lfs support should be enabled for this repo
EnableLFS bool `json:"enableLfs,omitempty" protobuf:"bytes,8,opt,name=enableLfs"`
// TLS client cert data for authenticating at the repo server
TLSClientCertData string `json:"tlsClientCertData,omitempty" protobuf:"bytes,9,opt,name=tlsClientCertData"`
// TLS client cert key for authenticating at the repo server
TLSClientCertKey string `json:"tlsClientCertKey,omitempty" protobuf:"bytes,10,opt,name=tlsClientCertKey"`
}
func (repo *Repository) IsInsecure() bool {
@ -919,6 +924,8 @@ func (m *Repository) CopyCredentialsFrom(source *Repository) {
m.InsecureIgnoreHostKey = source.InsecureIgnoreHostKey
m.Insecure = source.Insecure
m.EnableLFS = source.EnableLFS
m.TLSClientCertData = source.TLSClientCertData
m.TLSClientCertKey = source.TLSClientCertKey
}
}

View file

@ -216,6 +216,10 @@ func GenerateManifests(root, path string, q *apiclient.ManifestRequest) (*apicli
appSourceType, err := GetAppSourceType(q.ApplicationSource, appPath)
creds := argo.GetRepoCreds(q.Repo)
repoURL := ""
if q.Repo != nil {
repoURL = q.Repo.Repo
}
switch appSourceType {
case v1alpha1.ApplicationSourceTypeKsonnet:
targetObjs, dest, err = ksShow(q.AppLabelKey, appPath, q.ApplicationSource.Ksonnet)
@ -241,7 +245,7 @@ func GenerateManifests(root, path string, q *apiclient.ManifestRequest) (*apicli
}
}
case v1alpha1.ApplicationSourceTypeKustomize:
k := kustomize.NewKustomizeApp(appPath, creds)
k := kustomize.NewKustomizeApp(appPath, creds, repoURL)
targetObjs, _, _, err = k.Build(q.ApplicationSource.Kustomize)
case v1alpha1.ApplicationSourceTypePlugin:
targetObjs, err = runConfigManagementPlugin(appPath, q, creds)
@ -681,7 +685,7 @@ func (s *Service) GetAppDetails(ctx context.Context, q *apiclient.RepoServerAppD
case v1alpha1.ApplicationSourceTypeKustomize:
res.Kustomize = &apiclient.KustomizeAppSpec{}
res.Kustomize.Path = q.Path
k := kustomize.NewKustomizeApp(appPath, argo.GetRepoCreds(q.Repo))
k := kustomize.NewKustomizeApp(appPath, argo.GetRepoCreds(q.Repo), q.Repo.Repo)
_, imageTags, images, err := k.Build(nil)
if err != nil {
return nil, err

View file

@ -291,7 +291,14 @@ func (s *Server) ValidateAccess(ctx context.Context, q *repositorypkg.RepoAccess
return nil, err
}
repo := &appsv1.Repository{Username: q.Username, Password: q.Password, SSHPrivateKey: q.SshPrivateKey, Insecure: q.Insecure}
repo := &appsv1.Repository{
Username: q.Username,
Password: q.Password,
SSHPrivateKey: q.SshPrivateKey,
Insecure: q.Insecure,
TLSClientCertData: q.TlsClientCertData,
TLSClientCertKey: q.TlsClientCertKey,
}
err := git.TestRepo(q.Repo, argo.GetRepoCreds(repo), q.Insecure, false)
if err != nil {
return nil, err

View file

@ -56,6 +56,10 @@ message RepoAccessQuery {
string sshPrivateKey = 4;
// Whether to skip certificate or host key validation
bool insecure = 5;
// TLS client cert data for accessing HTTPS repository
string tlsClientCertData = 6;
// TLS client cert key for accessing HTTPS repository
string tlsClientCertKey = 7;
}
message RepoResponse {}

View file

@ -24,6 +24,7 @@ func TestCustomToolWithGitCreds(t *testing.T) {
},
},
).
CustomCACertAdded().
// add the private repo
HTTPSRepoURLAdded().
RepoURLType(RepoURLTypeHTTPS).

View file

@ -6,6 +6,7 @@ import (
. "github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/test/e2e/fixture"
"github.com/argoproj/argo-cd/test/e2e/fixture/certs"
"github.com/argoproj/argo-cd/test/e2e/fixture/repos"
"github.com/argoproj/argo-cd/util/settings"
)
@ -36,13 +37,38 @@ func Given(t *testing.T) *Context {
return &Context{t: t, destServer: KubernetesInternalAPIServerAddr, repoURLType: fixture.RepoURLTypeFile, name: fixture.Name(), timeout: 5, project: "default", prune: true}
}
func (c *Context) CustomCACertAdded() *Context {
certs.AddCustomCACert()
return c
}
func (c *Context) HTTPSRepoURLAdded() *Context {
repos.AddHTTPSRepo()
repos.AddHTTPSRepo(false)
return c
}
func (c *Context) HTTPSInsecureRepoURLAdded() *Context {
repos.AddHTTPSRepo(true)
return c
}
func (c *Context) HTTPSInsecureRepoURLWithClientCertAdded() *Context {
repos.AddHTTPSRepoClientCert(false)
return c
}
func (c *Context) HTTPSRepoURLWithClientCertAdded() *Context {
repos.AddHTTPSRepoClientCert(true)
return c
}
func (c *Context) SSHRepoURLAdded() *Context {
repos.AddSSHRepo()
repos.AddSSHRepo(false)
return c
}
func (c *Context) SSHInsecureRepoURLAdded() *Context {
repos.AddSSHRepo(true)
return c
}

View file

@ -0,0 +1,17 @@
package certs
import (
"path/filepath"
"github.com/argoproj/argo-cd/errors"
"github.com/argoproj/argo-cd/test/e2e/fixture"
)
func AddCustomCACert() {
caCertPath, err := filepath.Abs("../fixture/certs/argocd-test-ca.crt")
errors.CheckError(err)
args := []string{"cert", "add-tls", "localhost", "--from", caCertPath}
errors.FailOnErr(fixture.RunCli(args...))
args = []string{"cert", "add-tls", "127.0.0.1", "--from", caCertPath}
errors.FailOnErr(fixture.RunCli(args...))
}

View file

@ -42,6 +42,8 @@ const (
// ensure all repos are in one directory tree, so we can easily clean them up
tmpDir = "/tmp/argo-e2e"
repoDir = "testdata.git"
GuestbookPath = "guestbook"
)
var (
@ -59,11 +61,12 @@ var (
type RepoURLType string
const (
RepoURLTypeFile = "file"
RepoURLTypeHTTPS = "https"
RepoURLTypeSSH = "ssh"
GitUsername = "admin"
GitPassword = "password"
RepoURLTypeFile = "file"
RepoURLTypeHTTPS = "https"
RepoURLTypeHTTPSClientCert = "https-cc"
RepoURLTypeSSH = "ssh"
GitUsername = "admin"
GitPassword = "password"
)
// getKubeConfig creates new kubernetes client config using specified config path and config overrides variables
@ -129,10 +132,16 @@ func repoDirectory() string {
func RepoURL(urlType RepoURLType) string {
switch urlType {
// Git server via SSH
case RepoURLTypeSSH:
return "ssh://root@localhost:2222/tmp/argo-e2e/testdata.git"
// Git server via HTTPS
case RepoURLTypeHTTPS:
return "https://localhost:9443/argo-e2e/testdata.git"
// Git server via HTTPS - Client Cert protected
case RepoURLTypeHTTPSClientCert:
return "https://localhost:9444/argo-e2e/testdata.git"
// Default - file based Git repository
default:
return fmt.Sprintf("file://%s", repoDirectory())
}
@ -171,6 +180,28 @@ func updateSettingConfigMap(updater func(cm *corev1.ConfigMap) error) {
errors.CheckError(err)
}
func updateTLSCertsConfigMap(updater func(cm *corev1.ConfigMap) error) {
cm, err := KubeClientset.CoreV1().ConfigMaps(ArgoCDNamespace).Get(common.ArgoCDTLSCertsConfigMapName, v1.GetOptions{})
errors.CheckError(err)
if cm.Data == nil {
cm.Data = make(map[string]string)
}
errors.CheckError(updater(cm))
_, err = KubeClientset.CoreV1().ConfigMaps(ArgoCDNamespace).Update(cm)
errors.CheckError(err)
}
func updateSSHKnownHostsConfigMap(updater func(cm *corev1.ConfigMap) error) {
cm, err := KubeClientset.CoreV1().ConfigMaps(ArgoCDNamespace).Get(common.ArgoCDKnownHostsConfigMapName, v1.GetOptions{})
errors.CheckError(err)
if cm.Data == nil {
cm.Data = make(map[string]string)
}
errors.CheckError(updater(cm))
_, err = KubeClientset.CoreV1().ConfigMaps(ArgoCDNamespace).Update(cm)
errors.CheckError(err)
}
func SetResourceOverrides(overrides map[string]v1alpha1.ResourceOverride) {
updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
if len(overrides) > 0 {
@ -235,6 +266,20 @@ func SetRepoCredentials(repos ...settings.RepoCredentials) {
})
}
func SetTLSCerts() {
updateTLSCertsConfigMap(func(cm *corev1.ConfigMap) error {
cm.Data = map[string]string{}
return nil
})
}
func SetSSHKnownHosts() {
updateSSHKnownHostsConfigMap(func(cm *corev1.ConfigMap) error {
cm.Data = map[string]string{}
return nil
})
}
func SetHelmRepoCredential(creds settings.HelmRepoCredentials) {
updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
yamlBytes, err := yaml.Marshal(creds)
@ -287,6 +332,7 @@ func EnsureCleanState(t *testing.T) {
SetRepoCredentials()
SetRepos()
SetResourceFilter(settings.ResourcesFilter{})
SetTLSCerts()
// remove tmp dir
CheckError(os.RemoveAll(tmpDir))
@ -299,6 +345,10 @@ func EnsureCleanState(t *testing.T) {
// create tmp dir
FailOnErr(Run("", "mkdir", "-p", tmpDir))
// create TLS and SSH certificate directories
FailOnErr(Run("", "mkdir", "-p", tmpDir+"/app/config/tls"))
FailOnErr(Run("", "mkdir", "-p", tmpDir+"/app/config/ssh"))
// set-up tmp repo, must have unique name
FailOnErr(Run("", "cp", "-Rf", "testdata", repoDirectory()))
FailOnErr(Run(repoDirectory(), "chmod", "777", "."))

View file

@ -8,14 +8,44 @@ import (
)
// sets the current repo as the default SSH test repo
func AddSSHRepo() {
func AddSSHRepo(insecure bool) {
keyPath, err := filepath.Abs("../fixture/testrepos/id_rsa")
errors.CheckError(err)
args := []string{"repo", "add", fixture.RepoURL(fixture.RepoURLTypeSSH), "--ssh-private-key-path", keyPath, "--insecure-ignore-host-key"}
args := []string{"repo", "add", fixture.RepoURL(fixture.RepoURLTypeSSH), "--ssh-private-key-path", keyPath}
if insecure {
args = append(args, "--insecure-ignore-host-key")
}
errors.FailOnErr(fixture.RunCli(args...))
}
// sets the current repo as the default HTTPS test repo
func AddHTTPSRepo() {
errors.FailOnErr(fixture.RunCli("repo", "add", fixture.RepoURL(fixture.RepoURLTypeHTTPS), "--username", fixture.GitUsername, "--password", fixture.GitPassword, "--insecure-skip-server-verification"))
func AddHTTPSRepo(insecure bool) {
// This construct is somewhat necessary to satisfy the compiler
var repoURLType fixture.RepoURLType = fixture.RepoURLTypeHTTPS
args := []string{"repo", "add", fixture.RepoURL(repoURLType), "--username", fixture.GitUsername, "--password", fixture.GitPassword}
if insecure {
args = append(args, "--insecure-skip-server-verification")
}
errors.FailOnErr(fixture.RunCli(args...))
}
// sets a HTTPS repo using TLS client certificate authentication
func AddHTTPSRepoClientCert(insecure bool) {
certPath, err := filepath.Abs("../fixture/certs/argocd-test-client.crt")
errors.CheckError(err)
keyPath, err := filepath.Abs("../fixture/certs/argocd-test-client.key")
errors.CheckError(err)
args := []string{
"repo",
"add",
fixture.RepoURL(fixture.RepoURLTypeHTTPSClientCert),
"--username", fixture.GitUsername,
"--password", fixture.GitPassword,
"--tls-client-cert-path", certPath,
"--tls-client-cert-key-path", keyPath,
}
if insecure {
args = append(args, "--insecure-skip-server-verification")
}
errors.FailOnErr(fixture.RunCli(args...))
}

View file

@ -117,7 +117,7 @@ func TestSyncStatusOptionIgnore(t *testing.T) {
func TestKustomizeSSHRemoteBase(t *testing.T) {
Given(t).
// not the best test, as we should have two remote repos both with the same SSH private key
SSHRepoURLAdded().
SSHInsecureRepoURLAdded().
RepoURLType(fixture.RepoURLTypeSSH).
Path("ssh-kustomize-base").
When().

View file

@ -13,7 +13,20 @@ import (
func TestCannotAddAppFromPrivateRepoWithoutCfg(t *testing.T) {
Given(t).
RepoURLType(fixture.RepoURLTypeHTTPS).
Path(guestbookPath).
Path(fixture.GuestbookPath).
When().
IgnoreErrors().
Create().
Then().
Expect(Error("", "repository not accessible"))
}
// make sure you cannot create an app from a private repo without set-up
func TestCannotAddAppFromClientCertRepoWithoutCfg(t *testing.T) {
Given(t).
HTTPSInsecureRepoURLAdded().
RepoURLType(fixture.RepoURLTypeHTTPSClientCert).
Path(fixture.GuestbookPath).
When().
IgnoreErrors().
Create().
@ -37,9 +50,9 @@ func TestCanAddAppFromPrivateRepoWithRepoCfg(t *testing.T) {
}
// make sure you can create an app from a private repo, if the creds are set-up in the CM
func TestCanAddAppFromPrivateRepoWithCredCfg(t *testing.T) {
func TestCanAddAppFromInsecurePrivateRepoWithCredCfg(t *testing.T) {
Given(t).
HTTPSRepoURLAdded().
HTTPSInsecureRepoURLAdded().
RepoURLType(fixture.RepoURLTypeHTTPS).
Path("https-kustomize-base").
And(func() {
@ -58,3 +71,53 @@ func TestCanAddAppFromPrivateRepoWithCredCfg(t *testing.T) {
Then().
Expect(Success(""))
}
// make sure we can create an app from a private repo, in a secure manner using
// a custom CA certificate bundle
func TestCanAddAppFromPrivateRepoWithCredCfg(t *testing.T) {
Given(t).
CustomCACertAdded().
HTTPSRepoURLAdded().
RepoURLType(fixture.RepoURLTypeHTTPS).
Path("https-kustomize-base").
And(func() {
secretName := fixture.CreateSecret(fixture.GitUsername, fixture.GitPassword)
FailOnErr(fixture.Run("", "kubectl", "patch", "cm", "argocd-cm",
"-n", fixture.ArgoCDNamespace,
"-p", fmt.Sprintf(
`{"data": {"repository.credentials": "- passwordSecret:\n key: password\n name: %s\n url: %s\n usernameSecret:\n key: username\n name: %s\n"}}`,
secretName,
fixture.RepoURL(fixture.RepoURLTypeHTTPS),
secretName,
)))
}).
When().
Create().
Then().
Expect(Success(""))
}
// make sure we can create an app from a private repo, in a secure manner using
// a custom CA certificate bundle
func TestCanAddAppFromClientCertRepoWithCredCfg(t *testing.T) {
Given(t).
CustomCACertAdded().
HTTPSRepoURLWithClientCertAdded().
RepoURLType(fixture.RepoURLTypeHTTPSClientCert).
Path("https-kustomize-base").
And(func() {
secretName := fixture.CreateSecret(fixture.GitUsername, fixture.GitPassword)
FailOnErr(fixture.Run("", "kubectl", "patch", "cm", "argocd-cm",
"-n", fixture.ArgoCDNamespace,
"-p", fmt.Sprintf(
`{"data": {"repository.credentials": "- passwordSecret:\n key: password\n name: %s\n url: %s\n usernameSecret:\n key: username\n name: %s\n"}}`,
secretName,
fixture.RepoURL(fixture.RepoURLTypeHTTPS),
secretName,
)))
}).
When().
Create().
Then().
Expect(Success(""))
}

View file

@ -11,7 +11,7 @@ import (
func TestCanAccessSSHRepo(t *testing.T) {
Given(t).
SSHRepoURLAdded().
SSHInsecureRepoURLAdded().
RepoURLType(fixture.RepoURLTypeSSH).
Path("config-map").
When().

View file

@ -0,0 +1,18 @@
# Testing certificates
This directory contains all TLS certificates used for testing ArgoCD, including
the E2E tests. It also contains the CA certificate and key used for signing the
certificates.
* `argocd-test-ca.crt` and `argocd-test-ca.key` are the CA
* `argocd-test-client.crt` and `argocd-test-client.key` is a client certificate/key pair, signed by the CA
* `argocd-test-server.crt` and `argocd-test-server.key` is a server certificate/key pair, signed by the CA, with its CN set to 'localhost', and SAN entry for IP 127.0.0.1.
All keys have no passphrase. All certs, including CA cert, are valid until July
2119, so there should be no worries about having to renew them.
You can use the CA certificate to sign more certificates, if you need additional
certificates for testing.
Needless to say, but should be mentioned anyway: Do not use these certs for
anything else except ArgoCD tests.

View file

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDVjCCAj6gAwIBAgIUBgTX1E6hLD+kMJ6tDKTfVWj308UwDQYJKoZIhvcNAQEL
BQAwGTEXMBUGA1UEAwwOQXJnb0NEIFRlc3QgQ0EwIBcNMTkwNzIwMTUzNjEzWhgP
MjExOTA2MjYxNTM2MTNaMBkxFzAVBgNVBAMMDkFyZ29DRCBUZXN0IENBMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw2OHfSVf/YYm9aID39PodB3BSqDG
SIRYReBirSk6c9fq7sLVGn6kLFZQbxmkxkDhela+JdhTquQFLj0XBI6FYL3gN/64
uQZx7A1gdBIACrkTjGZTJQ5ifufGJZPM8x1SFMU41NOPJxBzy3F0SWV4CG+DwPTc
i31vtje340sCNlBP+GdlXvUs0tVnhKuhKeBmsi4Z0sECehEKoO3l3iNWHDEh5sa6
sS+oRVT2YwnzX/nqQYTjHxbUZZ7mGbfzXkyLH+BDdwO96hc9Qm3tukTJkP5ArPAa
R2lKi+YziORdSlcYbK0TYW5sY2DJQM7bmcz+iFWuYBDe+zQBry/Ib2VnbwIDAQAB
o4GTMIGQMB0GA1UdDgQWBBQesUBqH6vVPaffB66rXDfDyxiWrDBUBgNVHSMETTBL
gBQesUBqH6vVPaffB66rXDfDyxiWrKEdpBswGTEXMBUGA1UEAwwOQXJnb0NEIFRl
c3QgQ0GCFAYE19ROoSw/pDCerQyk31Vo99PFMAwGA1UdEwQFMAMBAf8wCwYDVR0P
BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQBgX1lyH/TqR1Fp1w4EcdBBsGyUKFI4
COW0pGzCXErlhz7r+Z9kJ75m8X0tDI1cYgUBHfzCliiIajuJcJ28HBPRgGgRujQI
INweSelauIZB4uVpnsCeomNj2xtpYV4j/dJ508HF1LEcsyKQvICSrzwCxIsnG1tG
o8QicVkGwCZDOPtKrHTf9IYgluh1KXX/by2LCxZ2S5BF7rlmA/eQOhXuvfgbmWEZ
hxBqiTtk2CEUqiEtwg1+0el8ds4dkDbmTnVwEABKAFMn/f3WBWcUN7zcdMN9taol
jJAI9NnYM28zg6jDCRdvX8IgT9Bc6k/n9mniFFthm0lN/vw17cewsYxb
-----END CERTIFICATE-----

View file

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEAw2OHfSVf/YYm9aID39PodB3BSqDGSIRYReBirSk6c9fq7sLV
Gn6kLFZQbxmkxkDhela+JdhTquQFLj0XBI6FYL3gN/64uQZx7A1gdBIACrkTjGZT
JQ5ifufGJZPM8x1SFMU41NOPJxBzy3F0SWV4CG+DwPTci31vtje340sCNlBP+Gdl
XvUs0tVnhKuhKeBmsi4Z0sECehEKoO3l3iNWHDEh5sa6sS+oRVT2YwnzX/nqQYTj
HxbUZZ7mGbfzXkyLH+BDdwO96hc9Qm3tukTJkP5ArPAaR2lKi+YziORdSlcYbK0T
YW5sY2DJQM7bmcz+iFWuYBDe+zQBry/Ib2VnbwIDAQABAoIBAQCpmNzkPeriLu3m
TGk8gb3F/IXo90DldSsCDFDaqWy5XPPpp2g+hSqP6aQLq6ChDXQqMtz+EJYfQalV
h2VRaTxQyr+zGwDQac287aeJWI58wEU9mxwUkDXineAagf5mEE9NBBvecHxTli5M
qwAbLV5RRCqC9Ify9EBhVF/jhRo4dV0VlL5Nol1wWr1hJUy6TwfFSUx6jo3nJb+i
zfag89cr/fq1bELBwpAU8GcS7tUXMi6Ka5Iy9yZdF6MolTxww5ydHxvoHI93IjlT
B564HRNRlrvfjsJJiDN0AfkOk0+bLKWahP/vgLz19olicbzAkjB+49KCuz5ht3TS
BSO8LMPBAoGBAOsc6+MAjn4gY2MJg24grBKlTy1ddZP8i5DJg3CcB7HAo2A71xHD
SQ9vTwa5lXDOfpQPn3k4MP3AWk0OvGsgf5pdmkWhaTa2nnlYR3e/Mp+SWXfsk9aq
2hxwMlBrI6TUafpd3tmzIhWFe04o/JKhQ15+iqLcz9dLsrvqfKfee30xAoGBANS/
LUKCQYPC+nmBaxUTQABmCuIJSyQ6mHagNXdFLM4qXrwnM3yMRp+kgm4xf1iysl3e
nUpMhl+mQgywVALWLZTHqCxDIxgOSIMEBbWWkqFPjrxI0o7M0UsESTxoRBTW0vMX
6UvwxcV6r8ctsk0QjjqEaff/Sll36O2qTTK5G4afAoGBAMzIzSFU68gp98dckvDK
XnaDLDrMS8xRTVMYYnckQINv0h3RIVwkZ6+c+Q1XejXedlwKW5IfAnUTJNQ3gKAi
l2JiIyyBEf6JA/GXlfBURgi9VVZiZsa02/OnOfvXxh0csw5S0vd10rtCH4uNoakp
818MAz8l65aQJacN7/Pbkl4RAoGBAKnjGFNaDNMZKkdJD6e+vYcT9XDXFwgROprQ
CKH7vXHM5+W8MCMvKV5mUjmIkTbVTBYlpNkmDs/mxiu3yv6Q5ck/Po5J9oZJMaJd
3elKGkFydHrqdCnQaPlMrObuJB+4BLBW0dfwpx2xgRi+5vkfVVRpVO3TER4s4PaY
3hi5NL/lAoGBAK1USYaglUf1dwIzUyJTNvWNmS/IAjkU1tmVJsSAFaZZgXFEWp5E
g8+S4Jq9n1BcqrVcxX4z9BmywJRgaQtftTAxfEqxsz+jOZPyrR6H/NlFfP6m6xDm
8Tf94woqk27HqgQK3z8sn8mu8njR9HSNjorNMN4fqsa+x+jc9IXgNT78
-----END RSA PRIVATE KEY-----

View file

@ -0,0 +1,85 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
33:af:0d:00:3e:e7:90:bb:8c:77:0e:24:82:e3:9d:61
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=ArgoCD Test CA
Validity
Not Before: Jul 20 15:39:35 2019 GMT
Not After : Jun 26 15:39:35 2119 GMT
Subject: CN=argo-test-client
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:96:1e:04:cc:35:14:87:98:83:58:87:21:89:2c:
40:a6:40:25:60:6a:d8:85:fd:b0:c2:d7:06:8d:4a:
0c:c3:ff:2a:98:77:60:f1:e6:74:49:b2:5d:8b:ef:
e3:80:0e:e3:6e:e0:50:7d:13:b8:fe:89:3a:ab:a5:
05:23:6d:ba:6d:f8:3e:b3:75:90:c8:41:3d:40:c8:
10:8d:62:a8:a9:4b:5a:6b:56:95:94:89:ca:28:f4:
7a:a4:77:07:3a:35:15:b1:79:04:3a:43:74:70:51:
e7:9c:41:29:92:d3:f3:69:67:1a:1d:4a:be:0d:6e:
72:bf:29:72:7d:1c:49:54:54:93:20:f3:7f:00:42:
e1:98:12:57:8e:39:a7:87:6f:f5:13:3c:25:be:9b:
00:5f:57:07:c8:f4:f8:b8:ca:36:fd:29:35:49:70:
31:66:0b:e7:a5:36:fb:10:ba:86:f9:18:98:17:d9:
7d:e0:60:4f:cf:08:85:57:8d:8b:e0:19:fe:83:28:
89:98:6f:2a:d5:85:ea:1e:59:0c:04:f5:87:b6:ff:
a2:8d:4c:62:ed:68:ba:9a:7f:2f:ac:94:c8:72:4e:
24:f7:37:54:19:f5:14:65:3e:65:ff:7b:e4:f9:c1:
42:80:a7:87:15:29:b9:78:26:f1:02:f2:4d:77:b8:
04:79
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Subject Key Identifier:
AB:D1:69:33:0A:6A:F4:46:B1:80:D3:F7:B2:93:E8:07:FB:03:E8:BF
X509v3 Authority Key Identifier:
keyid:1E:B1:40:6A:1F:AB:D5:3D:A7:DF:07:AE:AB:5C:37:C3:CB:18:96:AC
DirName:/CN=ArgoCD Test CA
serial:06:04:D7:D4:4E:A1:2C:3F:A4:30:9E:AD:0C:A4:DF:55:68:F7:D3:C5
X509v3 Extended Key Usage:
TLS Web Client Authentication
X509v3 Key Usage:
Digital Signature
Signature Algorithm: sha256WithRSAEncryption
03:a8:d4:3b:17:66:fc:93:20:b0:68:f9:79:fe:cd:e0:54:d6:
a4:20:fe:14:75:c9:63:f0:a3:ff:4a:a5:b8:d9:c8:43:fa:0b:
9c:da:0b:fd:23:b9:cb:c0:9e:5a:db:72:21:9e:c5:56:81:32:
14:4e:d5:ef:9b:97:ab:b8:93:1f:79:41:b0:fa:66:93:28:93:
95:54:4a:8a:27:26:8a:fe:81:fd:a5:68:f2:9b:9c:6c:63:c3:
96:98:9a:e9:e5:6d:34:69:f1:ea:ca:78:10:e4:2b:e1:41:bf:
dc:b6:c8:ba:76:ea:17:69:3e:cf:75:b8:28:03:17:06:0f:e5:
9a:cb:36:27:85:d7:b8:13:92:69:1c:ce:72:fb:71:1f:38:a2:
22:fa:86:13:20:44:79:77:9f:ab:11:e8:6e:65:94:b7:ee:c4:
39:bd:89:45:4c:55:80:92:a6:83:83:83:75:3c:30:4e:da:6b:
4b:74:0e:a7:86:4e:4f:79:d3:d2:a6:38:d5:ea:7d:fc:5f:a7:
73:3b:97:ef:cb:49:08:10:96:13:64:68:48:d0:b3:eb:59:93:
9d:22:ba:0c:83:1c:74:a1:f6:61:34:b6:8e:e5:e1:25:5a:09:
ec:7e:b0:b9:fd:21:7e:65:5d:3b:15:d7:a0:a3:e1:4f:fa:4e:
77:90:3c:83
-----BEGIN CERTIFICATE-----
MIIDZjCCAk6gAwIBAgIQM68NAD7nkLuMdw4kguOdYTANBgkqhkiG9w0BAQsFADAZ
MRcwFQYDVQQDDA5BcmdvQ0QgVGVzdCBDQTAgFw0xOTA3MjAxNTM5MzVaGA8yMTE5
MDYyNjE1MzkzNVowGzEZMBcGA1UEAwwQYXJnby10ZXN0LWNsaWVudDCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAJYeBMw1FIeYg1iHIYksQKZAJWBq2IX9
sMLXBo1KDMP/Kph3YPHmdEmyXYvv44AO427gUH0TuP6JOqulBSNtum34PrN1kMhB
PUDIEI1iqKlLWmtWlZSJyij0eqR3Bzo1FbF5BDpDdHBR55xBKZLT82lnGh1Kvg1u
cr8pcn0cSVRUkyDzfwBC4ZgSV445p4dv9RM8Jb6bAF9XB8j0+LjKNv0pNUlwMWYL
56U2+xC6hvkYmBfZfeBgT88IhVeNi+AZ/oMoiZhvKtWF6h5ZDAT1h7b/oo1MYu1o
upp/L6yUyHJOJPc3VBn1FGU+Zf975PnBQoCnhxUpuXgm8QLyTXe4BHkCAwEAAaOB
pTCBojAJBgNVHRMEAjAAMB0GA1UdDgQWBBSr0WkzCmr0RrGA0/eyk+gH+wPovzBU
BgNVHSMETTBLgBQesUBqH6vVPaffB66rXDfDyxiWrKEdpBswGTEXMBUGA1UEAwwO
QXJnb0NEIFRlc3QgQ0GCFAYE19ROoSw/pDCerQyk31Vo99PFMBMGA1UdJQQMMAoG
CCsGAQUFBwMCMAsGA1UdDwQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAA6jUOxdm
/JMgsGj5ef7N4FTWpCD+FHXJY/Cj/0qluNnIQ/oLnNoL/SO5y8CeWttyIZ7FVoEy
FE7V75uXq7iTH3lBsPpmkyiTlVRKiicmiv6B/aVo8pucbGPDlpia6eVtNGnx6sp4
EOQr4UG/3LbIunbqF2k+z3W4KAMXBg/lmss2J4XXuBOSaRzOcvtxHziiIvqGEyBE
eXefqxHobmWUt+7EOb2JRUxVgJKmg4ODdTwwTtprS3QOp4ZOT3nT0qY41ep9/F+n
czuX78tJCBCWE2RoSNCz61mTnSK6DIMcdKH2YTS2juXhJVoJ7H6wuf0hfmVdOxXX
oKPhT/pOd5A8gw==
-----END CERTIFICATE-----

View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCWHgTMNRSHmINY
hyGJLECmQCVgatiF/bDC1waNSgzD/yqYd2Dx5nRJsl2L7+OADuNu4FB9E7j+iTqr
pQUjbbpt+D6zdZDIQT1AyBCNYqipS1prVpWUicoo9Hqkdwc6NRWxeQQ6Q3RwUeec
QSmS0/NpZxodSr4NbnK/KXJ9HElUVJMg838AQuGYEleOOaeHb/UTPCW+mwBfVwfI
9Pi4yjb9KTVJcDFmC+elNvsQuob5GJgX2X3gYE/PCIVXjYvgGf6DKImYbyrVheoe
WQwE9Ye2/6KNTGLtaLqafy+slMhyTiT3N1QZ9RRlPmX/e+T5wUKAp4cVKbl4JvEC
8k13uAR5AgMBAAECggEAQNWsOso+GKY9LDIIwOb08RjJS9A5vf0op64Y7VLrGoeN
TRZaL3/Z/65iirrL5hYIEm4dNTgccQqx5Uo7YubUWwSZiAahxmuu2djOlVHkCGI8
JhnaNrIgNvoIMhoabABbYzAiLEvP8WbegnT+UKTr/z0BYV9ToBdwxbFP+ksKPLo1
DZYJyKFA+h9UCDR3/woPSOp57Ta/S26BZSqCYeX7PKkzUn8OnfBG16wPfSbMQzM1
AMwNG0VnQWpkbAhISLRcFUhyQC6kBqu8QEBHYa6SZ0qosGxZhnVo18bhz55q/6s/
GHf5nKCkN8rBOt5G22tJ22L1s7DLjrRmeyvNogPctQKBgQDFz5ZU/bWAmGFG60lJ
5KtMoGo4Us/8j4sjwMDaoujGO/lsWKejhL4ERfqXm4/yCtDGtq+eOgXN4rOGr/b2
SW4CCsJYdgiUdCH6A7pmUk9zKT0226Z7YcsBX14pttqni2NTcIvvWKjBDxo9r00f
OaQJbER3s0cwojvBiZs9bDE9zwKBgQDCRsyfiPyp+7NJ2u2Kx9KFIW6j3cOA5K5/
ixpolsubL1McvTWL6JFcZQ+y7/oQbmX860CFgtKhMOsdOH2AY4UwG5A1BIvdxgwH
E6RJAwa1j/KXS8NjtTGKn7ILPwwlYyFGzCpiaDoqeGIjXD5G5bXMZ+LNMKSdXuw2
/mOHDVSzNwKBgQCl4VTh5PhF5IL+4+RLsRTtZ0BsBxYfZ4h47PVM43xscHLTpuy9
tV1bXAuvA2cMxIEbgqt29pVTeB6wffONyToVQEyFvkWnqFOtw16W28OAgT6yODQ+
F14TwpPGS27FPaCHokPW7PRnIXER9WWpH78tn7sy3gZ/BC00OV8TfR02BQKBgQDB
Fz8vXSbLB/j1cdvgsKRjX4wo4DP8+s0YxTfVNfNd5RZ1HPWIfflj3KpSzcM7644A
aA1z5UfVn9o+67OJFOD+pJaaq08ceEfiDLqfOpvDfzO8/jdP9Xos7nY2SU6YJkOf
qzKBJliRd58KyBa5vnwHkkVQbYVfSEX8jrB7PVuu1wKBgQCLE6GkaJo2rwhMXGe+
0EzjFwo9wH/zY17xfFzj0rnJ07A16Q3dAFz+BXF90BWQZeWGxOa2NhlStBEAskU4
7PR5bV0u+G4EnD1TQbrh4jgNswrCCJylK7GjZutB+JCbA3UY3ih8QUlnlhbyoEk9
xDNMYUwlZfcAqlI/KbmKsEm6/w==
-----END PRIVATE KEY-----

View file

@ -0,0 +1,87 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
07:14:21:36:78:ab:8d:02:47:c2:91:06:df:39:88:fb
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=ArgoCD Test CA
Validity
Not Before: Jul 20 16:09:27 2019 GMT
Not After : Jun 26 16:09:27 2119 GMT
Subject: CN=localhost
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:d0:a6:e4:53:d3:25:34:29:e5:b2:e7:ad:e9:7f:
94:e5:1b:d8:0d:91:ca:89:58:d9:0c:d1:0e:46:bd:
3c:df:72:0e:2b:df:97:5e:76:f9:d8:9d:03:b7:db:
cd:20:e0:80:ec:2d:b7:8a:a4:8b:4d:d7:2e:a6:5d:
31:e3:36:d0:e4:73:c4:57:8e:88:cb:ff:5b:fa:73:
bc:2c:f0:ec:fb:9a:7c:b2:f0:09:3f:74:57:33:bb:
ac:85:19:21:5d:f0:fb:d1:a6:18:c7:1b:99:32:f9:
a9:ed:b1:5a:92:d3:59:7b:de:4a:57:3a:f5:be:f9:
37:78:cd:36:36:65:33:07:d9:6b:28:e2:88:8e:98:
6e:d0:d5:d3:47:4f:1e:3e:a3:6a:df:71:ce:8f:b7:
6b:66:c0:2c:48:8d:16:cd:1b:77:43:9a:45:0a:54:
78:39:a5:aa:bd:4a:21:a4:18:e2:34:18:b8:77:f4:
61:3f:4a:6c:5e:4d:58:10:33:f5:2e:22:bc:48:60:
85:76:90:f2:9b:c7:97:29:67:23:79:22:6d:81:76:
ab:fe:91:b1:c3:b0:47:52:38:ad:6b:c6:b2:07:fe:
49:37:70:a7:54:c4:02:fa:9d:b1:5b:c5:f9:5f:81:
66:cd:2d:bd:1d:72:d8:4b:64:e7:ae:b4:b8:0d:0e:
16:4b
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Subject Key Identifier:
C5:21:05:C6:C4:8E:DD:7A:E3:F7:02:61:C0:11:68:AD:7B:47:78:39
X509v3 Authority Key Identifier:
keyid:1E:B1:40:6A:1F:AB:D5:3D:A7:DF:07:AE:AB:5C:37:C3:CB:18:96:AC
DirName:/CN=ArgoCD Test CA
serial:06:04:D7:D4:4E:A1:2C:3F:A4:30:9E:AD:0C:A4:DF:55:68:F7:D3:C5
X509v3 Extended Key Usage:
TLS Web Server Authentication
X509v3 Key Usage:
Digital Signature, Key Encipherment
X509v3 Subject Alternative Name:
DNS:localhost, IP Address:127.0.0.1
Signature Algorithm: sha256WithRSAEncryption
6d:c6:8c:49:48:a8:f8:67:06:e3:45:93:ba:b8:86:54:11:40:
6e:1b:a8:cf:54:06:df:a7:1e:07:37:49:e9:71:54:65:d3:21:
ee:db:a5:13:44:91:d8:71:9b:ae:8d:d2:b4:b2:9c:02:ad:e3:
da:0b:24:63:c9:2f:c8:a0:31:f9:57:0e:83:09:8d:cd:07:50:
bc:9c:84:f1:7b:be:22:64:d4:ad:06:24:11:bd:55:b6:3e:31:
c8:02:02:d2:7f:0f:e8:11:12:6f:b5:58:f6:64:2d:98:1b:42:
78:f5:4c:7e:32:d7:72:20:9c:b8:79:d9:46:d5:71:5f:e8:1f:
77:62:50:e8:a3:e6:3a:bc:53:0b:a6:c7:1f:ad:d2:a4:48:f9:
f7:5f:24:f7:72:36:75:17:29:63:43:05:dd:e1:b3:70:78:e1:
ed:c8:08:01:67:f6:9f:3f:a4:2c:2d:b1:d4:0b:de:93:69:38:
8c:11:dd:92:85:bd:16:9e:5b:bd:6f:93:21:8e:7e:24:58:8c:
4f:be:96:ab:97:6f:bc:30:11:e7:de:37:5d:ff:f3:56:51:cf:
1b:6b:c3:b6:de:a4:a2:6a:f5:a9:74:03:d8:e5:fc:58:f7:ec:
2c:59:e9:fb:2a:4a:42:00:cf:67:f4:14:b1:1c:fd:87:b8:3f:
c6:94:ba:02
-----BEGIN CERTIFICATE-----
MIIDezCCAmOgAwIBAgIQBxQhNnirjQJHwpEG3zmI+zANBgkqhkiG9w0BAQsFADAZ
MRcwFQYDVQQDDA5BcmdvQ0QgVGVzdCBDQTAgFw0xOTA3MjAxNjA5MjdaGA8yMTE5
MDYyNjE2MDkyN1owFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA0KbkU9MlNCnlsuet6X+U5RvYDZHKiVjZDNEORr08
33IOK9+XXnb52J0Dt9vNIOCA7C23iqSLTdcupl0x4zbQ5HPEV46Iy/9b+nO8LPDs
+5p8svAJP3RXM7ushRkhXfD70aYYxxuZMvmp7bFaktNZe95KVzr1vvk3eM02NmUz
B9lrKOKIjphu0NXTR08ePqNq33HOj7drZsAsSI0WzRt3Q5pFClR4OaWqvUohpBji
NBi4d/RhP0psXk1YEDP1LiK8SGCFdpDym8eXKWcjeSJtgXar/pGxw7BHUjita8ay
B/5JN3CnVMQC+p2xW8X5X4FmzS29HXLYS2TnrrS4DQ4WSwIDAQABo4HBMIG+MAkG
A1UdEwQCMAAwHQYDVR0OBBYEFMUhBcbEjt164/cCYcARaK17R3g5MFQGA1UdIwRN
MEuAFB6xQGofq9U9p98HrqtcN8PLGJasoR2kGzAZMRcwFQYDVQQDDA5BcmdvQ0Qg
VGVzdCBDQYIUBgTX1E6hLD+kMJ6tDKTfVWj308UwEwYDVR0lBAwwCgYIKwYBBQUH
AwEwCwYDVR0PBAQDAgWgMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkq
hkiG9w0BAQsFAAOCAQEAbcaMSUio+GcG40WTuriGVBFAbhuoz1QG36ceBzdJ6XFU
ZdMh7tulE0SR2HGbro3StLKcAq3j2gskY8kvyKAx+VcOgwmNzQdQvJyE8Xu+ImTU
rQYkEb1Vtj4xyAIC0n8P6BESb7VY9mQtmBtCePVMfjLXciCcuHnZRtVxX+gfd2JQ
6KPmOrxTC6bHH63SpEj5918k93I2dRcpY0MF3eGzcHjh7cgIAWf2nz+kLC2x1Ave
k2k4jBHdkoW9Fp5bvW+TIY5+JFiMT76Wq5dvvDAR5943Xf/zVlHPG2vDtt6komr1
qXQD2OX8WPfsLFnp+ypKQgDPZ/QUsRz9h7g/xpS6Ag==
-----END CERTIFICATE-----

View file

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDQpuRT0yU0KeWy
563pf5TlG9gNkcqJWNkM0Q5GvTzfcg4r35dedvnYnQO3280g4IDsLbeKpItN1y6m
XTHjNtDkc8RXjojL/1v6c7ws8Oz7mnyy8Ak/dFczu6yFGSFd8PvRphjHG5ky+ant
sVqS01l73kpXOvW++Td4zTY2ZTMH2Wso4oiOmG7Q1dNHTx4+o2rfcc6Pt2tmwCxI
jRbNG3dDmkUKVHg5paq9SiGkGOI0GLh39GE/SmxeTVgQM/UuIrxIYIV2kPKbx5cp
ZyN5Im2Bdqv+kbHDsEdSOK1rxrIH/kk3cKdUxAL6nbFbxflfgWbNLb0dcthLZOeu
tLgNDhZLAgMBAAECggEAWg7e2Gt3UGeRFEI5Z2JA4w68l0bJE7U8ofKN5eyCHLO+
+CLe09AokN1coHwHstwT6VzmLrpMwkFRik8JXFfpm+F36o7D7sD+c0CHBSLMNuD8
V8ORSkkt5k9n7F7nbwO3vUqiwTXhTwZB2S1Gub7tjbBph/MT5OTuk36HmrzdZNIj
yNvYzbItaQc+2bk+cKM6Ws7oX5Uf3Wrw5816VbxKzX5dqcaqSSWnOzxChFvFgOzV
NmkX9zTDaVkWiWOG5W7eoiNCGd5yaJiISQsWqA7fjRZSq0vVAKYuCsde0CyGwr7A
U0HPePmqY9ame/KPdqxIidX88XAcT1c8zkM0lvAJGQKBgQD8l8SFWVQqKgVlBXAJ
I8kKiiWfFq4fP1XcO+c3RJP0Jv2q7RUaWUAiuRCeuBXuvsMkVKJ3NLuyTma/Ml0J
zIQRGAIM4WfgfKr2ebw0G9ZH29pAohOGjNfkrOSz3kjc2pWXjEVyHVoCZ5gfl4Ia
CCM+ciZg6YLCGh+IpHHYmIfDPQKBgQDTd2QiviP1WAJTMFKKxCf4zL9WUgah+JnU
8XwyPX1FiXAFE3YehL8yO07hhFcQVwXBbnXLYHal4SAX6qBxweo34bKSRlRDrfDh
bzWmBWqPFLO5v2NCmrhup9lpwDbYKVTv4P9c1KYum4mbeyYl8F74YUS/u2GMzIU3
5NbHpvI4JwKBgQDiCZJkXn7xyG7Rax9KHoru8pceGXayIcRc9AJCNA1OpIHGLqj+
zjWVnRThZAzEfFM2tMI6eKcqtfw+Rvx5I1pfzaMwEZoaipWXQXMJEowb/AMAacmd
U3ZzfcCfsWB0uuq5Fl6i8t80Xa4FP1i+oN+hJxdvDCfmOOhVKAt073I2ZQKBgQDF
BhcZO7j7DU3VPk+ZlNt9Nx8KiRA+9wY03e4OTTYbhMsHH7PNCXeukI2VFp2bzsjB
CW7c13qUYOVrAyML+lWETVf4a2h1SmoFUH1WuvMmbm5poQUVeFxgVSj/G6S9z/yg
Jy0ly8ct46LZZ2sKrCOAHfhU/3wLGD8C7cajbEt/vwKBgQDvis//d39w3QR/K0eQ
e3Sm01odTU7MfD7Tu+LGQK59RGz3LJ1YX0uRGAoF9TY5a2zgG6V42kXiva2jvctF
y+Y3Ta2zF+lrn1LDYHDR1mQ4lYxDXXESF+FAVP8kEV+CKQBKFbVKvKrBafSMUNjX
/0wJDDjjxnPLbxcBS9RaffdUsA==
-----END PRIVATE KEY-----

View file

@ -13,8 +13,38 @@ http {
auth_basic_user_file .htpasswd;
root /tmp/argo-e2e;
ssl_certificate localhost.crt;
ssl_certificate_key localhost.key;
ssl_certificate ../certs/argocd-test-server.crt;
ssl_certificate_key ../certs/argocd-test-server.key;
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
location ~ /argo-e2e(/.*) {
# Set chunks to unlimited, as the body's can be huge
client_max_body_size 0;
fastcgi_param SCRIPT_FILENAME /usr/lib/git-core/git-http-backend;
include /etc/nginx/fastcgi_params;
fastcgi_param GIT_HTTP_EXPORT_ALL "";
fastcgi_param GIT_PROJECT_ROOT /tmp/argo-e2e;
fastcgi_param PATH_INFO $1;
# Forward REMOTE_USER as we want to know when we are authenticated
fastcgi_param REMOTE_USER $remote_user;
fastcgi_pass unix:/var/run/fcgiwrap.socket;
}
}
# server block for strict verification of client certs
server {
listen 0.0.0.0:444 ssl http2;
auth_basic "Restricted";
auth_basic_user_file .htpasswd;
root /tmp/argo-e2e;
ssl_certificate ../certs/argocd-test-server.crt;
ssl_certificate_key ../certs/argocd-test-server.key;
ssl_client_certificate ../certs/argocd-test-ca.crt;
ssl_verify_client on;
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
docker run --name e2e-git --rm -i \
-p 2222:2222 -p 9080:80 -p 9443:443 \
-p 2222:2222 -p 9080:80 -p 9443:443 -p 9444:444 -p 9445:445 \
-w /go/src/github.com/argoproj/argo-cd -v $(pwd):/go/src/github.com/argoproj/argo-cd -v /tmp:/tmp argoproj/argo-cd-ci-builder:v1.0.0 \
bash -c "goreman -f ./test/fixture/testrepos/Procfile start"

View file

@ -146,12 +146,13 @@ func getCAPath(repoURL string) string {
return ""
}
// TODO: Figure out the correct way to distinguish between SSH and HTTPS repos
func GetRepoCreds(repo *argoappv1.Repository) git.Creds {
if repo == nil {
return git.NopCreds{}
}
if repo.Username != "" && repo.Password != "" {
return git.NewHTTPSCreds(repo.Username, repo.Password, repo.IsInsecure())
if (repo.Username != "" && repo.Password != "") || (repo.TLSClientCertData != "" && repo.TLSClientCertKey != "") {
return git.NewHTTPSCreds(repo.Username, repo.Password, repo.TLSClientCertData, repo.TLSClientCertKey, repo.IsInsecure())
}
if repo.SSHPrivateKey != "" {
return git.NewSSHCreds(repo.SSHPrivateKey, getCAPath(repo.Repo), repo.IsInsecure())

View file

@ -16,6 +16,8 @@ import (
"strings"
"golang.org/x/crypto/ssh"
"github.com/argoproj/argo-cd/common"
)
// A struct representing an entry in the list of SSH known hosts.
@ -59,10 +61,32 @@ const (
CertificateMaxLines = 128
// Maximum number of certificates or known host entries in a stream
CertificateMaxEntriesPerStream = 256
// Local path where certificate data is stored
CertificateDataPath = "/app/config/tls"
)
// Get the configured path to where TLS certificates are stored on the local
// filesystem. If ARGOCD_TLS_DATA_PATH environment is set, path is taken from
// there, otherwise the default will be returned.
func GetTLSCertificateDataPath() string {
envPath := os.Getenv(common.EnvVarTLSDataPath)
if envPath != "" {
return envPath
} else {
return common.DefaultPathTLSConfig
}
}
// Get the configured path to where SSH certificates are stored on the local
// filesystem. If ARGOCD_SSH_DATA_PATH environment is set, path is taken from
// there, otherwise the default will be returned.
func GetSSHKnownHostsDataPath() string {
envPath := os.Getenv(common.EnvVarSSHDataPath)
if envPath != "" {
return envPath + "/" + common.DefaultSSHKnownHostsName
} else {
return common.DefaultPathSSHConfig + "/" + common.DefaultSSHKnownHostsName
}
}
// Decode a certificate in PEM format to X509 data structure
func DecodePEMCertificateToX509(pemData string) (*x509.Certificate, error) {
decodedData, _ := pem.Decode([]byte(pemData))
@ -232,10 +256,15 @@ func SSHFingerprintSHA256(key ssh.PublicKey) string {
return strings.TrimRight(b64hash, "=")
}
// Remove possible port number from hostname and return just the FQDN
func ServerNameWithoutPort(serverName string) string {
return strings.Split(serverName, ":")[0]
}
// Load certificate data from a file. If the file does not exist, we do not
// consider it an error and just return empty data.
func GetCertificateForConnect(serverName string) ([]string, error) {
certPath := fmt.Sprintf("%s/%s", CertificateDataPath, serverName)
certPath := fmt.Sprintf("%s/%s", GetTLSCertificateDataPath(), ServerNameWithoutPort(serverName))
certificates, err := ParseTLSCertificatesFromPath(certPath)
if err != nil {
if os.IsNotExist(err) {
@ -252,8 +281,11 @@ func GetCertificateForConnect(serverName string) ([]string, error) {
return certificates, nil
}
// Gets the full path for a certificate bundle configured from a ConfigMap
// mount. This function makes sure that the path returned actually contain
// at least one valid certificate, and no invalid data.
func GetCertBundlePathForRepository(serverName string) (string, error) {
certPath := fmt.Sprintf("%s/%s", CertificateDataPath, serverName)
certPath := fmt.Sprintf("%s/%s", GetTLSCertificateDataPath(), ServerNameWithoutPort(serverName))
certs, err := GetCertificateForConnect(serverName)
if err != nil {
return "", nil
@ -264,6 +296,8 @@ func GetCertBundlePathForRepository(serverName string) (string, error) {
return certPath, nil
}
// Convert a list of certificates in PEM format to a x509.CertPool object,
// usable for most golang TLS functions.
func GetCertPoolFromPEMData(pemData []string) *x509.CertPool {
certPool := x509.NewCertPool()
for _, pem := range pemData {

View file

@ -335,3 +335,10 @@ func Test_SSHFingerprintSHA256(t *testing.T) {
assert.Equal(t, fp, fingerprints[idx])
}
}
func Test_ServerNameWithoutPort(t *testing.T) {
hostNameList := []string{"localhost", "localhost:9443", "localhost:", "localhost:abc"}
for _, hostName := range hostNameList {
assert.Equal(t, "localhost", ServerNameWithoutPort(hostName))
}
}

View file

@ -20,9 +20,16 @@ import (
)
const (
username = "username"
password = "password"
// The name of the key storing the username in the secret
username = "username"
// The name of the key storing the password in the secret
password = "password"
// The name of the key storing the SSH private in the secret
sshPrivateKey = "sshPrivateKey"
// The name of the key storing the TLS client cert data in the secret
tlsClientCertData = "tlsClientCertData"
// The name of the key storing the TLS client cert key in the secret
tlsClientCertKey = "tlsClientCertKey"
)
// ListRepoURLs returns list of repositories
@ -127,9 +134,11 @@ func (db *db) credentialsToRepository(repoInfo settings.RepoCredentials) (*appsv
EnableLFS: repoInfo.EnableLFS,
}
err := db.unmarshalFromSecretsStr(map[*string]*apiv1.SecretKeySelector{
&repo.Username: repoInfo.UsernameSecret,
&repo.Password: repoInfo.PasswordSecret,
&repo.SSHPrivateKey: repoInfo.SSHPrivateKeySecret,
&repo.Username: repoInfo.UsernameSecret,
&repo.Password: repoInfo.PasswordSecret,
&repo.SSHPrivateKey: repoInfo.SSHPrivateKeySecret,
&repo.TLSClientCertData: repoInfo.TLSClientCertDataSecret,
&repo.TLSClientCertKey: repoInfo.TLSClientCertKeySecret,
}, make(map[string]*apiv1.Secret))
return repo, err
@ -178,9 +187,11 @@ func (db *db) DeleteRepository(ctx context.Context, repoURL string) error {
return status.Errorf(codes.NotFound, "repo '%s' not found", repoURL)
}
err = db.updateSecrets(&repos[index], &appsv1.Repository{
SSHPrivateKey: "",
Password: "",
Username: "",
SSHPrivateKey: "",
Password: "",
Username: "",
TLSClientCertData: "",
TLSClientCertKey: "",
})
if err != nil {
return err
@ -221,6 +232,8 @@ func (db *db) updateSecrets(repoInfo *settings.RepoCredentials, r *appsv1.Reposi
repoInfo.UsernameSecret = setSecretData(repoInfo.UsernameSecret, r.Username, username)
repoInfo.PasswordSecret = setSecretData(repoInfo.PasswordSecret, r.Password, password)
repoInfo.SSHPrivateKeySecret = setSecretData(repoInfo.SSHPrivateKeySecret, r.SSHPrivateKey, sshPrivateKey)
repoInfo.TLSClientCertDataSecret = setSecretData(repoInfo.TLSClientCertDataSecret, r.TLSClientCertData, tlsClientCertData)
repoInfo.TLSClientCertKeySecret = setSecretData(repoInfo.TLSClientCertKeySecret, r.TLSClientCertKey, tlsClientCertKey)
for k, v := range secretsData {
err := db.upsertSecret(k, v)
if err != nil {
@ -251,7 +264,7 @@ func (db *db) upsertSecret(name string, data map[string][]byte) error {
}
}
} else {
for _, key := range []string{username, password, sshPrivateKey} {
for _, key := range []string{username, password, sshPrivateKey, tlsClientCertData, tlsClientCertKey} {
if secret.Data == nil {
secret.Data = make(map[string][]byte)
}

View file

@ -91,15 +91,41 @@ func (f *factory) NewClient(rawRepoURL string, path string, creds Creds, insecur
// a client with those certificates in the list of root CAs used to verify
// the server's certificate.
// - Otherwise (and on non-fatal errors), a default HTTP client is returned.
func getRepoHTTPClient(repoURL string, insecure bool) transport.Transport {
func GetRepoHTTPClient(repoURL string, insecure bool, creds Creds) *http.Client {
// Default HTTP client
var customHTTPClient transport.Transport = githttp.NewClient(&http.Client{})
var customHTTPClient *http.Client = &http.Client{}
// Callback function to return any configured client certificate
// We never return err, but an empty cert instead.
clientCertFunc := func(req *tls.CertificateRequestInfo) (*tls.Certificate, error) {
var err error
cert := tls.Certificate{}
// If we aren't called with HTTPSCreds, then we just return an empty cert
httpsCreds, ok := creds.(HTTPSCreds)
if !ok {
return &cert, nil
}
// If the creds contain client certificate data, we return a TLS.Certificate
// populated with the cert and its key.
if httpsCreds.clientCertData != "" && httpsCreds.clientCertKey != "" {
cert, err = tls.X509KeyPair([]byte(httpsCreds.clientCertData), []byte(httpsCreds.clientCertKey))
if err != nil {
log.Errorf("Could not load Client Certificate: %v", err)
return &cert, nil
}
}
return &cert, nil
}
if insecure {
customHTTPClient = githttp.NewClient(&http.Client{
customHTTPClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
InsecureSkipVerify: true,
GetClientCertificate: clientCertFunc,
},
},
// 15 second timeout
@ -109,7 +135,7 @@ func getRepoHTTPClient(repoURL string, insecure bool) transport.Transport {
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
})
}
} else {
parsedURL, err := url.Parse(repoURL)
if err != nil {
@ -120,10 +146,11 @@ func getRepoHTTPClient(repoURL string, insecure bool) transport.Transport {
return customHTTPClient
} else if len(serverCertificatePem) > 0 {
certPool := certutil.GetCertPoolFromPEMData(serverCertificatePem)
customHTTPClient = githttp.NewClient(&http.Client{
customHTTPClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: certPool,
RootCAs: certPool,
GetClientCertificate: clientCertFunc,
},
},
// 15 second timeout
@ -132,9 +159,23 @@ func getRepoHTTPClient(repoURL string, insecure bool) transport.Transport {
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
})
}
} else {
// else no custom certificate stored.
customHTTPClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
GetClientCertificate: clientCertFunc,
},
},
// 15 second timeout
Timeout: 15 * time.Second,
// don't follow redirect
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
}
// else no custom certificate stored.
}
return customHTTPClient
@ -290,7 +331,7 @@ func (m *nativeGitClient) LsRemote(revision string) (string, error) {
return "", err
}
//refs, err := remote.List(&git.ListOptions{Auth: auth})
refs, err := listRemote(remote, &git.ListOptions{Auth: auth}, m.insecure)
refs, err := listRemote(remote, &git.ListOptions{Auth: auth}, m.insecure, m.creds)
if err != nil {
return "", err
}
@ -401,6 +442,27 @@ func (m *nativeGitClient) runCmdOutput(cmd *exec.Cmd) (string, error) {
cmd.Env = append(cmd.Env, "HOME=/dev/null")
// Skip LFS for most Git operations except when explicitly requested
cmd.Env = append(cmd.Env, "GIT_LFS_SKIP_SMUDGE=1")
// For HTTPS repositories, we need to consider insecure repositories as well
// as custom CA bundles from the cert database.
if IsHTTPSURL(m.repoURL) {
if m.insecure {
cmd.Env = append(cmd.Env, "GIT_SSL_NO_VERIFY=true")
} else {
parsedURL, err := url.Parse(m.repoURL)
// We don't fail if we cannot parse the URL, but log a warning in that
// case. And we execute the command in a verbatim way.
if err != nil {
log.Warnf("runCmdOutput: Could not parse repo URL '%s'", m.repoURL)
} else {
caPath, err := certutil.GetCertBundlePathForRepository(parsedURL.Host)
if err == nil && caPath != "" {
cmd.Env = append(cmd.Env, fmt.Sprintf("GIT_SSL_CAINFO=%s", caPath))
}
}
}
}
log.Debug(strings.Join(cmd.Args, " "))
return argoexec.RunCommandExt(cmd, argoconfig.CmdOpts())
}

View file

@ -33,21 +33,86 @@ func (c NopCreds) Environ() (io.Closer, []string, error) {
// HTTPS creds implementation
type HTTPSCreds struct {
// Username for authentication
username string
// Password for authentication
password string
// Whether to ignore invalid server certificates
insecure bool
// Client certificate to use
clientCertData string
// Client certificate key to use
clientCertKey string
}
func NewHTTPSCreds(username, password string, insecure bool) HTTPSCreds {
return HTTPSCreds{username, password, insecure}
func NewHTTPSCreds(username, password, clientCertData, clientCertKey string, insecure bool) HTTPSCreds {
return HTTPSCreds{
username,
password,
insecure,
clientCertData,
clientCertKey,
}
}
// Get additional required environment variables for executing git client to
// access specific repository via HTTPS.
func (c HTTPSCreds) Environ() (io.Closer, []string, error) {
env := []string{fmt.Sprintf("GIT_ASKPASS=%s", "git-ask-pass.sh"), fmt.Sprintf("GIT_USERNAME=%s", c.username), fmt.Sprintf("GIT_PASSWORD=%s", c.password)}
httpCloser := authFilePaths(make([]string, 0))
// GIT_SSL_NO_VERIFY is used to tell git not to validate the server's cert at
// all.
if c.insecure {
env = append(env, "GIT_SSL_NO_VERIFY=true")
}
return NopCloser{}, env, nil
// In case the repo is configured for using a TLS client cert, we need to make
// sure git client will use it. The certificate's key must not be password
// protected.
if c.clientCertData != "" && c.clientCertKey != "" {
var certFile, keyFile *os.File
// We need to actually create two temp files, one for storing cert data and
// another for storing the key. If we fail to create second fail, the first
// must be removed.
certFile, err := ioutil.TempFile(util.TempDir, "")
if err == nil {
defer certFile.Close()
keyFile, err = ioutil.TempFile(util.TempDir, "")
if err != nil {
removeErr := os.Remove(certFile.Name())
if removeErr != nil {
log.Errorf("Could not remove previously created tempfile %s: %v", certFile.Name(), removeErr)
}
return NopCloser{}, nil, err
}
defer keyFile.Close()
} else {
return NopCloser{}, nil, err
}
// We should have both temp files by now
httpCloser = authFilePaths([]string{certFile.Name(), keyFile.Name()})
_, err = certFile.WriteString(c.clientCertData)
if err != nil {
httpCloser.Close()
return NopCloser{}, nil, err
}
// GIT_SSL_CERT is the full path to a client certificate to be used
env = append(env, fmt.Sprintf("GIT_SSL_CERT=%s", certFile.Name()))
_, err = keyFile.WriteString(c.clientCertKey)
if err != nil {
httpCloser.Close()
return NopCloser{}, nil, err
}
// GIT_SSL_KEY is the full path to a client certificate's key to be used
env = append(env, fmt.Sprintf("GIT_SSL_KEY=%s", keyFile.Name()))
}
return httpCloser, env, nil
}
// SSH implementation
@ -63,24 +128,39 @@ func NewSSHCreds(sshPrivateKey string, caPath string, insecureIgnoreHostKey bool
type sshPrivateKeyFile string
type authFilePaths []string
func (f sshPrivateKeyFile) Close() error {
return os.Remove(string(f))
}
// Remove a list of files that have been created as temp files while creating
// HTTPCreds object above.
func (f authFilePaths) Close() error {
var retErr error = nil
for _, path := range f {
err := os.Remove(path)
if err != nil {
log.Errorf("HTTPSCreds.Close(): Could not remove temp file %s: %v", path, err)
retErr = err
}
}
return retErr
}
func (c SSHCreds) Environ() (io.Closer, []string, error) {
// use the SHM temp dir from util, more secure
file, err := ioutil.TempFile(util.TempDir, "")
if err != nil {
return nil, nil, err
}
defer file.Close()
_, err = file.WriteString(c.sshPrivateKey)
if err != nil {
return nil, nil, err
}
err = file.Close()
if err != nil {
return nil, nil, err
}
args := []string{"ssh", "-i", file.Name()}
var env []string
if c.caPath != "" {

View file

@ -3,7 +3,9 @@ package git
import (
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@ -107,6 +109,70 @@ func TestSameURL(t *testing.T) {
}
}
func TestCustomHTTPClient(t *testing.T) {
certFile, err := filepath.Abs("../../test/fixture/certs/argocd-test-client.crt")
assert.NoError(t, err)
assert.NotEqual(t, "", certFile)
keyFile, err := filepath.Abs("../../test/fixture/certs/argocd-test-client.key")
assert.NoError(t, err)
assert.NotEqual(t, "", keyFile)
certData, err := ioutil.ReadFile(certFile)
assert.NoError(t, err)
assert.NotEqual(t, "", string(certData))
keyData, err := ioutil.ReadFile(keyFile)
assert.NoError(t, err)
assert.NotEqual(t, "", string(keyData))
// Get HTTPSCreds with client cert creds specified, and insecure connection
creds := NewHTTPSCreds("test", "test", string(certData), string(keyData), false)
client := GetRepoHTTPClient("https://localhost:9443/foo/bar", false, creds)
assert.NotNil(t, client)
assert.NotNil(t, client.Transport)
if client.Transport != nil {
httpClient := client.Transport.(*http.Transport)
assert.NotNil(t, httpClient.TLSClientConfig)
assert.Equal(t, false, httpClient.TLSClientConfig.InsecureSkipVerify)
assert.NotNil(t, httpClient.TLSClientConfig.GetClientCertificate)
if httpClient.TLSClientConfig.GetClientCertificate != nil {
cert, err := httpClient.TLSClientConfig.GetClientCertificate(nil)
assert.NoError(t, err)
if err == nil {
assert.NotNil(t, cert)
assert.NotEqual(t, 0, len(cert.Certificate))
assert.NotNil(t, cert.PrivateKey)
}
}
}
// Get HTTPSCreds without client cert creds, but insecure connection
creds = NewHTTPSCreds("test", "test", "", "", true)
client = GetRepoHTTPClient("https://localhost:9443/foo/bar", true, creds)
assert.NotNil(t, client)
assert.NotNil(t, client.Transport)
if client.Transport != nil {
httpClient := client.Transport.(*http.Transport)
assert.NotNil(t, httpClient.TLSClientConfig)
assert.Equal(t, true, httpClient.TLSClientConfig.InsecureSkipVerify)
assert.NotNil(t, httpClient.TLSClientConfig.GetClientCertificate)
if httpClient.TLSClientConfig.GetClientCertificate != nil {
cert, err := httpClient.TLSClientConfig.GetClientCertificate(nil)
assert.NoError(t, err)
if err == nil {
assert.NotNil(t, cert)
assert.Equal(t, 0, len(cert.Certificate))
assert.Nil(t, cert.PrivateKey)
}
}
}
}
func TestLsRemote(t *testing.T) {
clnt, err := NewFactory().NewClient("https://github.com/argoproj/argo-cd.git", "/tmp", NopCreds{}, false, false)
assert.NoError(t, err)

View file

@ -5,6 +5,7 @@ import (
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/transport"
"gopkg.in/src-d/go-git.v4/plumbing/transport/client"
"gopkg.in/src-d/go-git.v4/plumbing/transport/http"
"gopkg.in/src-d/go-git.v4/utils/ioutil"
)
@ -12,8 +13,8 @@ import (
// As workaround methods `newUploadPackSession`, `newClient` and `listRemote` were copied from https://github.com/src-d/go-git/blob/master/remote.go and modified to use
// transport with InsecureSkipVerify flag is verification should be disabled.
func newUploadPackSession(url string, auth transport.AuthMethod, insecureSkipTLSVerify bool) (transport.UploadPackSession, error) {
c, ep, err := newClient(url, insecureSkipTLSVerify)
func newUploadPackSession(url string, auth transport.AuthMethod, insecure bool, creds Creds) (transport.UploadPackSession, error) {
c, ep, err := newClient(url, insecure, creds)
if err != nil {
return nil, err
}
@ -21,7 +22,7 @@ func newUploadPackSession(url string, auth transport.AuthMethod, insecureSkipTLS
return c.NewUploadPackSession(ep, auth)
}
func newClient(url string, insecureSkipTLSVerify bool) (transport.Transport, *transport.Endpoint, error) {
func newClient(url string, insecure bool, creds Creds) (transport.Transport, *transport.Endpoint, error) {
ep, err := transport.NewEndpoint(url)
if err != nil {
return nil, nil, err
@ -31,7 +32,7 @@ func newClient(url string, insecureSkipTLSVerify bool) (transport.Transport, *tr
// For HTTPS repositories, we get a custom Transport. Everything else will
// be default.
if IsHTTPSURL(url) {
c = getRepoHTTPClient(url, insecureSkipTLSVerify)
c = http.NewClient(GetRepoHTTPClient(url, insecure, creds))
} else {
c, err = client.NewClient(ep)
if err != nil {
@ -41,8 +42,8 @@ func newClient(url string, insecureSkipTLSVerify bool) (transport.Transport, *tr
return c, ep, err
}
func listRemote(r *git.Remote, o *git.ListOptions, insecureSkipTLSVerify bool) (rfs []*plumbing.Reference, err error) {
s, err := newUploadPackSession(r.Config().URLs[0], o.Auth, insecureSkipTLSVerify)
func listRemote(r *git.Remote, o *git.ListOptions, insecure bool, creds Creds) (rfs []*plumbing.Reference, err error) {
s, err := newUploadPackSession(r.Config().URLs[0], o.Auth, insecure, creds)
if err != nil {
return nil, err
}

View file

@ -3,6 +3,7 @@ package kustomize
import (
"fmt"
"io/ioutil"
"net/url"
"os"
"os/exec"
"path/filepath"
@ -19,6 +20,8 @@ import (
"github.com/argoproj/argo-cd/util/config"
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/kube"
certutil "github.com/argoproj/argo-cd/util/cert"
)
type ImageTag struct {
@ -45,16 +48,21 @@ type Kustomize interface {
}
// NewKustomizeApp create a new wrapper to run commands on the `kustomize` command-line tool.
func NewKustomizeApp(path string, creds git.Creds) Kustomize {
func NewKustomizeApp(path string, creds git.Creds, fromRepo string) Kustomize {
return &kustomize{
path: path,
creds: creds,
repo: fromRepo,
}
}
type kustomize struct {
path string
// path inside the checked out tree
path string
// creds structure
creds git.Creds
// the Git repository URL where we checked out
repo string
}
func (k *kustomize) Build(opts *v1alpha1.ApplicationSourceKustomize) ([]*unstructured.Unstructured, []ImageTag, []Image, error) {
@ -135,6 +143,28 @@ func (k *kustomize) Build(opts *v1alpha1.ApplicationSourceKustomize) ([]*unstruc
return nil, nil, nil, err
}
defer func() { _ = closer.Close() }()
// If we were passed a HTTPS URL, make sure that we also check whether there
// is a custom CA bundle configured for connecting to the server.
if k.repo != "" && git.IsHTTPSURL(k.repo) {
parsedURL, err := url.Parse(k.repo)
if err != nil {
log.Warnf("Could not parse URL %s: %v", k.repo, err)
} else {
caPath, err := certutil.GetCertBundlePathForRepository(parsedURL.Host)
if err != nil {
// Some error while getting CA bundle
log.Warnf("Could not get CA bundle path for %s: %v", parsedURL.Host, err)
} else if caPath == "" {
// No cert configured
log.Debugf("No caCert found for repo %s", parsedURL.Host)
} else {
// Make Git use CA bundle
environ = append(environ, fmt.Sprintf("GIT_SSL_CAINFO=%s", caPath))
}
}
}
cmd.Env = append(cmd.Env, environ...)
out, err := argoexec.RunCommandExt(cmd, config.CmdOpts())
if err != nil {

View file

@ -33,7 +33,7 @@ func TestKustomizeBuild(t *testing.T) {
appPath, err := testDataDir()
assert.Nil(t, err)
namePrefix := "namePrefix-"
kustomize := NewKustomizeApp(appPath, git.NopCreds{})
kustomize := NewKustomizeApp(appPath, git.NopCreds{}, "")
kustomizeSource := v1alpha1.ApplicationSourceKustomize{
NamePrefix: namePrefix,
ImageTags: []v1alpha1.KustomizeImageTag{

View file

@ -7,6 +7,7 @@ import (
"crypto/x509"
"encoding/base64"
"fmt"
"io/ioutil"
"os"
"strings"
"sync"
@ -27,6 +28,7 @@ import (
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util"
certutil "github.com/argoproj/argo-cd/util/cert"
"github.com/argoproj/argo-cd/util/password"
tlsutil "github.com/argoproj/argo-cd/util/tls"
)
@ -96,6 +98,10 @@ type RepoCredentials struct {
Insecure bool `json:"insecure,omitempty"`
// Whether the repo is git-lfs enabled
EnableLFS bool `json:"enableLfs,omitempty"`
// Name of the secret storing the TLS client cert data
TLSClientCertDataSecret *apiv1.SecretKeySelector `json:"tlsClientCertDataSecret,omitempty"`
// Name of the secret storing the TLS client cert's key data
TLSClientCertKeySecret *apiv1.SecretKeySelector `json:"tlsClientCertKeySecret,omitempty"`
}
type HelmRepoCredentials struct {
@ -658,10 +664,23 @@ func (mgr *SettingsManager) SaveSSHKnownHostsData(ctx context.Context, knownHost
certCM.Data = make(map[string]string)
}
certCM.Data["ssh_known_hosts"] = strings.Join(knownHostsList, "\n")
sshKnownHostsData := strings.Join(knownHostsList, "\n") + "\n"
certCM.Data["ssh_known_hosts"] = sshKnownHostsData
_, err = mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Update(certCM)
if err != nil {
return nil
return err
}
// If we are running outside a K8S cluster, the ConfigMap mount will not
// automatically write out the SSH known hosts data. We need this mechanism
// for running E2E tests.
if os.Getenv(common.EnvVarFakeInClusterConfig) == "true" {
knownHostsPath := certutil.GetSSHKnownHostsDataPath()
err := ioutil.WriteFile(knownHostsPath, []byte(sshKnownHostsData), 0644)
// We don't return error, but let the user know through log
if err != nil {
log.Errorf("Could not write SSH known hosts data: %v", err)
}
}
return mgr.ResyncInformers()
@ -681,7 +700,43 @@ func (mgr *SettingsManager) SaveTLSCertificateData(ctx context.Context, tlsCerti
certCM.Data = tlsCertificates
_, err = mgr.clientset.CoreV1().ConfigMaps(mgr.namespace).Update(certCM)
if err != nil {
return nil
return err
}
// If we are running outside a K8S cluster, the ConfigMap mount will not
// automatically write out the TLS certificate data. We need this mechanism
// for running E2E tests.
//
// The paradigm here is, that we first remove all files in the TLS data
// directory (there should be only TLS certs in it), and then recreate each
// single cert that is still in the tlsCertificates data map.
if os.Getenv(common.EnvVarFakeInClusterConfig) == "true" {
tlsDataPath := certutil.GetTLSCertificateDataPath()
// First throw way everything
tlsFiles, err := ioutil.ReadDir(tlsDataPath)
if err != nil {
log.Errorf("Could not open TLS certificate dir %s: %v", tlsDataPath, err)
} else {
for _, file := range tlsFiles {
tlsCertPath := fmt.Sprintf("%s/%s", tlsDataPath, file.Name())
log.Debugf("Deleting TLS certificate file %s", tlsCertPath)
err := os.Remove(tlsCertPath)
if err != nil {
log.Errorf("Could not delete TLS cert file %s: %v", tlsCertPath, err)
}
}
}
// Recreate configured TLS certificates
for hostName, certData := range tlsCertificates {
certPath := fmt.Sprintf("%s/%s", tlsDataPath, hostName)
log.Debugf("Writing TLS Certificate data to %s", certPath)
err := ioutil.WriteFile(certPath, []byte(certData), 0644)
if err != nil {
log.Errorf("Could not write PEM data to %s: %v", certPath, err)
}
}
}
return mgr.ResyncInformers()