mirror of
https://github.com/arangodb/kube-arangodb.git
synced 2024-12-14 11:57:37 +00:00
[Feature] Integration TLS (#1710)
This commit is contained in:
parent
1095567432
commit
efbbc79439
25 changed files with 672 additions and 180 deletions
186
.golangci.yaml
186
.golangci.yaml
|
@ -23,100 +23,102 @@ linters-settings:
|
|||
importas:
|
||||
no-unaliased: true
|
||||
alias:
|
||||
- pkg: k8s.io/api/core/v1
|
||||
alias: core
|
||||
- pkg: k8s.io/apimachinery/pkg/apis/meta/v1
|
||||
alias: meta
|
||||
- pkg: k8s.io/client-go/kubernetes/typed/core/v1
|
||||
alias: typedCore
|
||||
- pkg: k8s.io/api/apps/v1
|
||||
alias: apps
|
||||
- pkg: k8s.io/api/batch/v1
|
||||
alias: batch
|
||||
- pkg: k8s.io/api/storage/v1
|
||||
alias: storage
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/deployment/reconcile/shared
|
||||
alias: sharedReconcile
|
||||
- pkg: k8s.io/api/policy/v1
|
||||
alias: policy
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/apis/shared/v1
|
||||
alias: sharedApi
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1
|
||||
alias: schedulerApi
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1/profiles
|
||||
alias: schedulerProfiles
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1/container
|
||||
alias: schedulerContainerApi
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1/container/resources
|
||||
alias: schedulerContainerResourcesApi
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1/pod
|
||||
alias: schedulerPodApi
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1/pod/resources
|
||||
alias: schedulerPodResourcesApi
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1
|
||||
alias: schedulerApiv1alpha1
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/profiles
|
||||
alias: schedulerProfilesv1alpha1
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/container
|
||||
alias: schedulerContainerApiv1alpha1
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/container/resources
|
||||
alias: schedulerContainerResourcesApiv1alpha1
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/pod
|
||||
alias: schedulerPodApiv1alpha1
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/pod/resources
|
||||
alias: schedulerPodResourcesApiv1alpha1
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/apis/shared
|
||||
alias: shared
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/handlers/enterprise/analytics/shared
|
||||
alias: analyticsShared
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/handlers/enterprise/shared
|
||||
alias: enterpriseShared
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/handlers/enterprise/ml/shared
|
||||
alias: mlShared
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/handlers/enterprise/ml/shared/test
|
||||
alias: mlSharedTests
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/apis/analytics/v1alpha1
|
||||
alias: analyticsApi
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1
|
||||
alias: networkingApi
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/apis/ml/v1beta1
|
||||
alias: mlApi
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/apis/ml/v1alpha1
|
||||
alias: mlApiv1alpha1
|
||||
- pkg: github.com/arangodb/kube-arangodb/integrations/scheduler/v1/definition
|
||||
alias: pbSchedulerV1
|
||||
- pkg: github.com/arangodb/kube-arangodb/integrations/scheduler/v1
|
||||
alias: pbImplSchedulerV1
|
||||
- pkg: github.com/arangodb/kube-arangodb/integrations/shutdown/v1/definition
|
||||
alias: pbShutdownV1
|
||||
- pkg: github.com/arangodb/kube-arangodb/integrations/shutdown/v1
|
||||
alias: pbImplShutdownV1
|
||||
- pkg: github.com/arangodb/kube-arangodb/integrations/authentication/v1/definition
|
||||
alias: pbAuthenticationV1
|
||||
- pkg: github.com/arangodb/kube-arangodb/integrations/authentication/v1
|
||||
alias: pbImplAuthenticationV1
|
||||
- pkg: github.com/arangodb/kube-arangodb/integrations/authorization/v0/definition
|
||||
alias: pbAuthorizationV0
|
||||
- pkg: github.com/arangodb/kube-arangodb/integrations/authorization/v0
|
||||
alias: pbImplAuthorizationV0
|
||||
- pkg: github.com/arangodb/kube-arangodb/integrations/config/v1/definition
|
||||
alias: pbConfigV1
|
||||
- pkg: github.com/arangodb/kube-arangodb/integrations/config/v1
|
||||
alias: pbImplConfigV1
|
||||
- pkg: github.com/arangodb/kube-arangodb/integrations/pong/v1/definition
|
||||
alias: pbPongV1
|
||||
- alias: pbImplAuthenticationV1
|
||||
pkg: github.com/arangodb/kube-arangodb/integrations/authentication/v1
|
||||
- alias: pbAuthenticationV1
|
||||
pkg: github.com/arangodb/kube-arangodb/integrations/authentication/v1/definition
|
||||
- alias: pbImplAuthorizationV0
|
||||
pkg: github.com/arangodb/kube-arangodb/integrations/authorization/v0
|
||||
- alias: pbAuthorizationV0
|
||||
pkg: github.com/arangodb/kube-arangodb/integrations/authorization/v0/definition
|
||||
- alias: pbImplConfigV1
|
||||
pkg: github.com/arangodb/kube-arangodb/integrations/config/v1
|
||||
- alias: pbConfigV1
|
||||
pkg: github.com/arangodb/kube-arangodb/integrations/config/v1/definition
|
||||
- alias: pbImplEnvoyAuthV3
|
||||
pkg: github.com/arangodb/kube-arangodb/integrations/envoy/auth/v3
|
||||
- pkg: github.com/arangodb/kube-arangodb/integrations/pong/v1
|
||||
alias: pbImplPongV1
|
||||
- pkg: github.com/arangodb/kube-arangodb/integrations/shared/v1/definition
|
||||
alias: pbSharedV1
|
||||
- pkg: github.com/arangodb/kube-arangodb/integrations/shared/v1
|
||||
alias: pbImplSharedV1
|
||||
- pkg: github.com/envoyproxy/go-control-plane/envoy/service/auth/v3
|
||||
alias: pbEnvoyAuthV3
|
||||
- pkg: github.com/arangodb/kube-arangodb/integrations/envoy/auth/v3
|
||||
alias: pbImplEnvoyAuthV3
|
||||
- pkg: github.com/arangodb/kube-arangodb/pkg/util/k8sutil/resources
|
||||
alias: kresources
|
||||
- pkg: github.com/arangodb/kube-arangodb/integrations/pong/v1/definition
|
||||
alias: pbPongV1
|
||||
- alias: pbImplSchedulerV1
|
||||
pkg: github.com/arangodb/kube-arangodb/integrations/scheduler/v1
|
||||
- alias: pbSchedulerV1
|
||||
pkg: github.com/arangodb/kube-arangodb/integrations/scheduler/v1/definition
|
||||
- alias: pbImplSharedV1
|
||||
pkg: github.com/arangodb/kube-arangodb/integrations/shared/v1
|
||||
- alias: pbSharedV1
|
||||
pkg: github.com/arangodb/kube-arangodb/integrations/shared/v1/definition
|
||||
- alias: pbImplShutdownV1
|
||||
pkg: github.com/arangodb/kube-arangodb/integrations/shutdown/v1
|
||||
- alias: pbShutdownV1
|
||||
pkg: github.com/arangodb/kube-arangodb/integrations/shutdown/v1/definition
|
||||
- alias: analyticsApi
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/apis/analytics/v1alpha1
|
||||
- alias: mlApiv1alpha1
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/apis/ml/v1alpha1
|
||||
- alias: mlApi
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/apis/ml/v1beta1
|
||||
- alias: networkingApi
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1
|
||||
- alias: schedulerApiv1alpha1
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1
|
||||
- alias: schedulerContainerApiv1alpha1
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/container
|
||||
- alias: schedulerContainerResourcesApiv1alpha1
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/container/resources
|
||||
- alias: schedulerPodApiv1alpha1
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/pod
|
||||
- alias: schedulerPodResourcesApiv1alpha1
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/pod/resources
|
||||
- alias: schedulerProfilesv1alpha1
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1alpha1/profiles
|
||||
- alias: schedulerApi
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1
|
||||
- alias: schedulerContainerApi
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1/container
|
||||
- alias: schedulerContainerResourcesApi
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1/container/resources
|
||||
- alias: schedulerPodApi
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1/pod
|
||||
- alias: schedulerPodResourcesApi
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1/pod/resources
|
||||
- alias: schedulerProfiles
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/apis/scheduler/v1beta1/profiles
|
||||
- alias: shared
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/apis/shared
|
||||
- alias: sharedApi
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/apis/shared/v1
|
||||
- alias: sharedReconcile
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/deployment/reconcile/shared
|
||||
- alias: analyticsShared
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/handlers/enterprise/analytics/shared
|
||||
- alias: mlShared
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/handlers/enterprise/ml/shared
|
||||
- alias: mlSharedTests
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/handlers/enterprise/ml/shared/test
|
||||
- alias: enterpriseShared
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/handlers/enterprise/shared
|
||||
- alias: kresources
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/util/k8sutil/resources
|
||||
- alias: ktls
|
||||
pkg: github.com/arangodb/kube-arangodb/pkg/util/k8sutil/tls
|
||||
- alias: pbEnvoyAuthV3
|
||||
pkg: github.com/envoyproxy/go-control-plane/envoy/service/auth/v3
|
||||
- alias: apps
|
||||
pkg: k8s.io/api/apps/v1
|
||||
- alias: batch
|
||||
pkg: k8s.io/api/batch/v1
|
||||
- alias: core
|
||||
pkg: k8s.io/api/core/v1
|
||||
- alias: policy
|
||||
pkg: k8s.io/api/policy/v1
|
||||
- alias: storage
|
||||
pkg: k8s.io/api/storage/v1
|
||||
- alias: meta
|
||||
pkg: k8s.io/apimachinery/pkg/apis/meta/v1
|
||||
- alias: typedCore
|
||||
pkg: k8s.io/client-go/kubernetes/typed/core/v1
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
- (Feature) Custom Gateway image
|
||||
- (Bugfix) Fix race condition in ArangoBackup
|
||||
- (Feature) Improve Gateway Config gen
|
||||
- (Feature) Integration Service TLS
|
||||
|
||||
## [1.2.42](https://github.com/arangodb/kube-arangodb/tree/1.2.42) (2024-07-23)
|
||||
- (Maintenance) Go 1.22.4 & Kubernetes 1.29.6 libraries
|
||||
|
|
|
@ -22,6 +22,7 @@ Flags:
|
|||
--health.auth.token string Token for health service (when auth service is token)
|
||||
--health.auth.type string Auth type for health service (default "None")
|
||||
--health.shutdown.enabled Determines if shutdown service should be enabled and exposed (default true)
|
||||
--health.tls.keyfile string Path to the keyfile
|
||||
-h, --help help for arangodb_operator_integration
|
||||
--integration.authentication.v1 Enable AuthenticationV1 Integration Service
|
||||
--integration.authentication.v1.enabled Defines if Authentication is enabled (default true)
|
||||
|
@ -74,6 +75,8 @@ Flags:
|
|||
--services.external.auth.token string Token for external service (when auth service is token)
|
||||
--services.external.auth.type string Auth type for external service (default "None")
|
||||
--services.external.enabled Defines if external access is enabled
|
||||
--services.external.tls.keyfile string Path to the keyfile
|
||||
--services.tls.keyfile string Path to the keyfile
|
||||
|
||||
Use "arangodb_operator_integration [command] --help" for more information about a command.
|
||||
```
|
||||
|
@ -94,6 +97,10 @@ Available Commands:
|
|||
Flags:
|
||||
--address string GRPC Service Address (default "127.0.0.1:8080")
|
||||
-h, --help help for client
|
||||
--tls.ca string Path to the custom CA
|
||||
--tls.enabled Defines if GRPC is protected with TLS
|
||||
--tls.fallback Enables TLS Fallback
|
||||
--tls.insecure Enables Insecure TLS Connection
|
||||
--token string GRPC Token
|
||||
|
||||
Use "arangodb_operator_integration client [command] --help" for more information about a command.
|
||||
|
|
|
@ -29,7 +29,6 @@ import (
|
|||
|
||||
pbPongV1 "github.com/arangodb/kube-arangodb/integrations/pong/v1/definition"
|
||||
pbSharedV1 "github.com/arangodb/kube-arangodb/integrations/shared/v1/definition"
|
||||
pbShutdownV1 "github.com/arangodb/kube-arangodb/integrations/shutdown/v1/definition"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/svc"
|
||||
)
|
||||
|
||||
|
@ -45,7 +44,7 @@ type impl struct {
|
|||
}
|
||||
|
||||
func (i *impl) Name() string {
|
||||
return pbShutdownV1.Name
|
||||
return pbPongV1.Name
|
||||
}
|
||||
|
||||
func (i *impl) Health() svc.HealthState {
|
||||
|
|
|
@ -49,7 +49,7 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
defaultTLSTTL = Duration("2610h") // About 3 month
|
||||
DefaultTLSTTL = Duration("2610h") // About 3 month
|
||||
)
|
||||
|
||||
// TLSSpec holds TLS specific configuration settings
|
||||
|
@ -157,7 +157,7 @@ func (s *TLSSpec) SetDefaults(defaultCASecretName string) {
|
|||
if s.GetTTL() == "" {
|
||||
// Note that we don't check for nil here, since even a specified, but zero
|
||||
// should result in the default value.
|
||||
s.TTL = NewDuration(defaultTLSTTL)
|
||||
s.TTL = NewDuration(DefaultTLSTTL)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
|
||||
// Copyright 2016-2024 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -60,6 +60,6 @@ func TestTLSSpecSetDefaults(t *testing.T) {
|
|||
assert.Equal(t, "foo", def(TLSSpec{CASecretName: util.NewType[string]("foo")}).GetCASecretName())
|
||||
assert.Len(t, def(TLSSpec{}).GetAltNames(), 0)
|
||||
assert.Len(t, def(TLSSpec{AltNames: []string{"foo.local"}}).GetAltNames(), 1)
|
||||
assert.Equal(t, defaultTLSTTL, def(TLSSpec{}).GetTTL())
|
||||
assert.Equal(t, DefaultTLSTTL, def(TLSSpec{}).GetTTL())
|
||||
assert.Equal(t, time.Hour, def(TLSSpec{TTL: NewDuration("1h")}).GetTTL().AsDuration())
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
defaultTLSTTL = Duration("2610h") // About 3 month
|
||||
DefaultTLSTTL = Duration("2610h") // About 3 month
|
||||
)
|
||||
|
||||
// TLSSpec holds TLS specific configuration settings
|
||||
|
@ -157,7 +157,7 @@ func (s *TLSSpec) SetDefaults(defaultCASecretName string) {
|
|||
if s.GetTTL() == "" {
|
||||
// Note that we don't check for nil here, since even a specified, but zero
|
||||
// should result in the default value.
|
||||
s.TTL = NewDuration(defaultTLSTTL)
|
||||
s.TTL = NewDuration(DefaultTLSTTL)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
|
||||
// Copyright 2016-2024 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -60,6 +60,6 @@ func TestTLSSpecSetDefaults(t *testing.T) {
|
|||
assert.Equal(t, "foo", def(TLSSpec{CASecretName: util.NewType[string]("foo")}).GetCASecretName())
|
||||
assert.Len(t, def(TLSSpec{}).GetAltNames(), 0)
|
||||
assert.Len(t, def(TLSSpec{AltNames: []string{"foo.local"}}).GetAltNames(), 1)
|
||||
assert.Equal(t, defaultTLSTTL, def(TLSSpec{}).GetTTL())
|
||||
assert.Equal(t, DefaultTLSTTL, def(TLSSpec{}).GetTTL())
|
||||
assert.Equal(t, time.Hour, def(TLSSpec{TTL: NewDuration("1h")}).GetTTL().AsDuration())
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ import (
|
|||
operatorHTTP "github.com/arangodb/kube-arangodb/pkg/util/http"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
|
||||
inspectorInterface "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector"
|
||||
memberTls "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/tls"
|
||||
ktls "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/tls"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/tools"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/strings"
|
||||
)
|
||||
|
@ -542,13 +542,13 @@ func (r *Reconciler) keyfileRenewalRequired(ctx context.Context, apiObject k8sut
|
|||
}
|
||||
|
||||
// Verify AltNames
|
||||
var altNames memberTls.KeyfileInput
|
||||
var altNames ktls.KeyfileInput
|
||||
|
||||
switch group.Type() {
|
||||
case api.ServerGroupTypeArangoD:
|
||||
altNames, err = memberTls.GetServerAltNames(apiObject, spec, tlsSpec, service, group, member)
|
||||
altNames, err = ktls.GetServerAltNames(apiObject, spec, tlsSpec, service, group, member)
|
||||
case api.ServerGroupTypeArangoSync:
|
||||
altNames, err = memberTls.GetSyncAltNames(apiObject, spec, tlsSpec, group, member)
|
||||
altNames, err = ktls.GetSyncAltNames(apiObject, spec, tlsSpec, group, member)
|
||||
default:
|
||||
assertion.InvalidGroupKey.Assert(true, "Unable to check TLS Key Renewal for an unknown group: %s", group.AsRole())
|
||||
return false, false
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
|
||||
// Copyright 2016-2024 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -23,36 +23,23 @@ package resources
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
certificates "github.com/arangodb-helper/go-certificates"
|
||||
|
||||
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/errors"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
|
||||
secretv1 "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/secret/v1"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/kerrors"
|
||||
)
|
||||
|
||||
const (
|
||||
clientAuthECDSACurve = "P256" // This curve is the default that ArangoDB accepts and plenty strong
|
||||
ktls "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/tls"
|
||||
)
|
||||
|
||||
// createClientAuthCACertificate creates a client authentication CA certificate and stores it in a secret with name
|
||||
// specified in the given spec.
|
||||
func (r *Resources) createClientAuthCACertificate(ctx context.Context, secrets secretv1.ModInterface, spec api.SyncAuthenticationSpec, deploymentName string, ownerRef *meta.OwnerReference) error {
|
||||
log := r.log.Str("section", "secrets")
|
||||
options := certificates.CreateCertificateOptions{
|
||||
CommonName: fmt.Sprintf("%s Client Authentication Root Certificate", deploymentName),
|
||||
ValidFrom: time.Now(),
|
||||
ValidFor: caTTL,
|
||||
IsCA: true,
|
||||
IsClientAuth: true,
|
||||
ECDSACurve: clientAuthECDSACurve,
|
||||
}
|
||||
cert, priv, err := certificates.CreateCertificate(options, nil)
|
||||
|
||||
cert, priv, err := ktls.CreateTLSCACertificate(fmt.Sprintf("%s Client Authentication Root Certificate", deploymentName))
|
||||
if err != nil {
|
||||
log.Err(err).Str("name", spec.GetClientCASecretName()).Debug("Failed to create CA certificate")
|
||||
return errors.WithStack(err)
|
||||
|
|
|
@ -23,27 +23,19 @@ package resources
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
certificates "github.com/arangodb-helper/go-certificates"
|
||||
|
||||
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
|
||||
"github.com/arangodb/kube-arangodb/pkg/logging"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/errors"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/globals"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
|
||||
inspectorInterface "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector"
|
||||
secretv1 "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/secret/v1"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/kerrors"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/tls"
|
||||
)
|
||||
|
||||
const (
|
||||
caTTL = time.Hour * 24 * 365 * 10 // 10 year
|
||||
tlsECDSACurve = "P256" // This curve is the default that ArangoDB accepts and plenty strong
|
||||
ktls "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/tls"
|
||||
)
|
||||
|
||||
// createTLSCACertificate creates a CA certificate and stores it in a secret with name
|
||||
|
@ -52,18 +44,12 @@ func (r *Resources) createTLSCACertificate(ctx context.Context, secrets secretv1
|
|||
deploymentName string, ownerRef *meta.OwnerReference) error {
|
||||
log := r.log.Str("section", "tls").Str("secret", spec.GetCASecretName())
|
||||
|
||||
options := certificates.CreateCertificateOptions{
|
||||
CommonName: fmt.Sprintf("%s Root Certificate", deploymentName),
|
||||
ValidFrom: time.Now(),
|
||||
ValidFor: caTTL,
|
||||
IsCA: true,
|
||||
ECDSACurve: tlsECDSACurve,
|
||||
}
|
||||
cert, priv, err := certificates.CreateCertificate(options, nil)
|
||||
cert, priv, err := ktls.CreateTLSCACertificate(fmt.Sprintf("%s Root Certificate", deploymentName))
|
||||
if err != nil {
|
||||
log.Err(err).Debug("Failed to create CA certificate")
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if err := k8sutil.CreateCASecret(ctx, secrets, spec.GetCASecretName(), cert, priv, ownerRef); err != nil {
|
||||
if kerrors.IsAlreadyExists(err) {
|
||||
log.Debug("CA Secret already exists")
|
||||
|
@ -78,9 +64,13 @@ func (r *Resources) createTLSCACertificate(ctx context.Context, secrets secretv1
|
|||
|
||||
// createTLSServerCertificate creates a TLS certificate for a specific server and stores
|
||||
// it in a secret with the given name.
|
||||
func createTLSServerCertificate(ctx context.Context, log logging.Logger, cachedStatus inspectorInterface.Inspector, secrets secretv1.ModInterface, names tls.KeyfileInput, spec api.TLSSpec,
|
||||
func createTLSServerCertificate(ctx context.Context, log logging.Logger, cachedStatus inspectorInterface.Inspector, secrets secretv1.ModInterface, names ktls.KeyfileInput, spec api.TLSSpec,
|
||||
secretName string, ownerRef *meta.OwnerReference) (bool, error) {
|
||||
log = log.Str("secret", secretName)
|
||||
// Setup defaults
|
||||
if names.TTL == nil {
|
||||
names.TTL = util.NewType(spec.GetTTL().AsDuration())
|
||||
}
|
||||
// Load CA certificate
|
||||
ctxChild, cancel := globals.GetGlobalTimeouts().Kubernetes().WithTimeout(ctx)
|
||||
defer cancel()
|
||||
|
@ -89,28 +79,11 @@ func createTLSServerCertificate(ctx context.Context, log logging.Logger, cachedS
|
|||
log.Err(err).Debug("Failed to load CA certificate")
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
ca, err := certificates.LoadCAFromPEM(caCert, caKey)
|
||||
if err != nil {
|
||||
log.Err(err).Debug("Failed to decode CA certificate")
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
options := certificates.CreateCertificateOptions{
|
||||
CommonName: names.AltNames[0],
|
||||
Hosts: names.AltNames,
|
||||
EmailAddresses: names.Email,
|
||||
ValidFrom: time.Now(),
|
||||
ValidFor: spec.GetTTL().AsDuration(),
|
||||
IsCA: false,
|
||||
ECDSACurve: tlsECDSACurve,
|
||||
}
|
||||
cert, priv, err := certificates.CreateCertificate(options, &ca)
|
||||
keyfile, err := ktls.CreateTLSServerKeyfile(caCert, caKey, names)
|
||||
if err != nil {
|
||||
log.Err(err).Debug("Failed to create server certificate")
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
keyfile := strings.TrimSpace(cert) + "\n" +
|
||||
strings.TrimSpace(priv)
|
||||
|
||||
err = globals.GetGlobalTimeouts().Kubernetes().RunWithTimeout(ctx, func(ctxChild context.Context) error {
|
||||
_, err := k8sutil.CreateTLSKeyfileSecret(ctxChild, secrets, secretName, keyfile, ownerRef)
|
||||
|
|
|
@ -51,7 +51,7 @@ import (
|
|||
podv1 "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/pod/v1"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/interfaces"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/kerrors"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/tls"
|
||||
ktls "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/tls"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/tolerations"
|
||||
)
|
||||
|
||||
|
@ -549,7 +549,7 @@ func (r *Resources) createPodForMember(ctx context.Context, cachedStatus inspect
|
|||
// Create TLS secret
|
||||
tlsKeyfileSecretName := k8sutil.CreateTLSKeyfileSecretName(apiObject.GetName(), role, m.ID)
|
||||
|
||||
names, err := tls.GetSyncAltNames(apiObject, spec, spec.Sync.TLS, group, m)
|
||||
names, err := ktls.GetSyncAltNames(apiObject, spec, spec.Sync.TLS, group, m)
|
||||
if err != nil {
|
||||
return errors.WithStack(errors.Wrapf(err, "Failed to render alt names"))
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ import (
|
|||
inspectorInterface "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector"
|
||||
secretv1 "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/secret/v1"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/kerrors"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/tls"
|
||||
ktls "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/tls"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/token"
|
||||
)
|
||||
|
||||
|
@ -146,7 +146,7 @@ func (r *Resources) EnsureSecrets(ctx context.Context, cachedStatus inspectorInt
|
|||
|
||||
tlsKeyfileSecretName := k8sutil.AppendTLSKeyfileSecretPostfix(member.GetName())
|
||||
if _, exists := cachedStatus.Secret().V1().GetSimple(tlsKeyfileSecretName); !exists {
|
||||
serverNames, err := tls.GetServerAltNames(apiObject, spec, spec.TLS, service, members[id].Group, members[id].Member)
|
||||
serverNames, err := ktls.GetServerAltNames(apiObject, spec, spec.TLS, service, members[id].Group, members[id].Member)
|
||||
if err != nil {
|
||||
return errors.WithStack(errors.Wrapf(err, "Failed to render alt names"))
|
||||
}
|
||||
|
|
|
@ -22,7 +22,10 @@ package clients
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc"
|
||||
|
@ -74,6 +77,39 @@ func client[T any](ctx context.Context, cfg *Config, in func(cc grpc.ClientConnI
|
|||
opts = append(opts, util.TokenAuthInterceptors(token)...)
|
||||
}
|
||||
|
||||
if cfg.TLS.Enabled {
|
||||
config := &tls.Config{}
|
||||
|
||||
if ca := cfg.TLS.CA; ca != "" {
|
||||
pemServerCA, err := os.ReadFile(ca)
|
||||
if err != nil {
|
||||
return util.Default[T](), nil, err
|
||||
}
|
||||
|
||||
certPool := x509.NewCertPool()
|
||||
if !certPool.AppendCertsFromPEM(pemServerCA) {
|
||||
return util.Default[T](), nil, err
|
||||
}
|
||||
|
||||
config.RootCAs = certPool
|
||||
}
|
||||
|
||||
if cfg.TLS.Insecure {
|
||||
config.InsecureSkipVerify = true
|
||||
}
|
||||
|
||||
if cfg.TLS.Fallback {
|
||||
client, closer, err := util.NewOptionalTLSGRPCClient(ctx, in, cfg.Address, config, opts...)
|
||||
if err != nil {
|
||||
return util.Default[T](), nil, err
|
||||
}
|
||||
|
||||
return client, closer, nil
|
||||
} else {
|
||||
opts = append(opts, util.ClientTLS(config)...)
|
||||
}
|
||||
}
|
||||
|
||||
client, closer, err := util.NewGRPCClient(ctx, in, cfg.Address, opts...)
|
||||
if err != nil {
|
||||
return util.Default[T](), nil, err
|
||||
|
|
|
@ -33,6 +33,10 @@ type Factory func(c *Config) Client
|
|||
type Config struct {
|
||||
Address string
|
||||
Token string
|
||||
TLS struct {
|
||||
Enabled, Insecure, Fallback bool
|
||||
CA string
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) Register(cmd *cobra.Command) error {
|
||||
|
@ -40,6 +44,10 @@ func (c *Config) Register(cmd *cobra.Command) error {
|
|||
|
||||
f.StringVar(&c.Address, "address", "127.0.0.1:8080", "GRPC Service Address")
|
||||
f.StringVar(&c.Token, "token", "", "GRPC Token")
|
||||
f.BoolVar(&c.TLS.Enabled, "tls.enabled", false, "Defines if GRPC is protected with TLS")
|
||||
f.StringVar(&c.TLS.CA, "tls.ca", "", "Path to the custom CA")
|
||||
f.BoolVar(&c.TLS.Insecure, "tls.insecure", false, "Enables Insecure TLS Connection")
|
||||
f.BoolVar(&c.TLS.Fallback, "tls.fallback", false, "Enables TLS Fallback")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
pbImplPongV1 "github.com/arangodb/kube-arangodb/integrations/pong/v1"
|
||||
pbImplShutdownV1 "github.com/arangodb/kube-arangodb/integrations/shutdown/v1"
|
||||
|
@ -73,6 +72,10 @@ type serviceConfiguration struct {
|
|||
|
||||
address string
|
||||
|
||||
tls struct {
|
||||
keyfile string
|
||||
}
|
||||
|
||||
auth struct {
|
||||
t string
|
||||
|
||||
|
@ -81,7 +84,9 @@ type serviceConfiguration struct {
|
|||
}
|
||||
|
||||
func (s *serviceConfiguration) Config() (svc.Configuration, error) {
|
||||
var opts []grpc.ServerOption
|
||||
var cfg svc.Configuration
|
||||
|
||||
cfg.Address = s.address
|
||||
|
||||
switch strings.ToLower(s.auth.t) {
|
||||
case "none":
|
||||
|
@ -91,16 +96,21 @@ func (s *serviceConfiguration) Config() (svc.Configuration, error) {
|
|||
return util.Default[svc.Configuration](), errors.Errorf("Token is empty")
|
||||
}
|
||||
|
||||
opts = append(opts,
|
||||
cfg.Options = append(cfg.Options,
|
||||
basicTokenAuthUnaryInterceptor(s.auth.token),
|
||||
basicTokenAuthStreamInterceptor(s.auth.token),
|
||||
)
|
||||
}
|
||||
|
||||
return svc.Configuration{
|
||||
Options: opts,
|
||||
Address: s.address,
|
||||
}, nil
|
||||
if keyfile := s.tls.keyfile; keyfile != "" {
|
||||
if tls, err := tlsServerOptions(keyfile); err != nil {
|
||||
return svc.Configuration{}, err
|
||||
} else {
|
||||
cfg.TLSOptions = tls
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (c *configuration) Register(cmd *cobra.Command) error {
|
||||
|
@ -120,16 +130,19 @@ func (c *configuration) Register(cmd *cobra.Command) error {
|
|||
f.BoolVar(&c.health.shutdownEnabled, "health.shutdown.enabled", true, "Determines if shutdown service should be enabled and exposed")
|
||||
f.StringVar(&c.health.auth.t, "health.auth.type", "None", "Auth type for health service")
|
||||
f.StringVar(&c.health.auth.token, "health.auth.token", "", "Token for health service (when auth service is token)")
|
||||
f.StringVar(&c.health.tls.keyfile, "health.tls.keyfile", "", "Path to the keyfile")
|
||||
|
||||
f.BoolVar(&c.services.internal.enabled, "services.enabled", true, "Defines if internal access is enabled")
|
||||
f.StringVar(&c.services.internal.address, "services.address", "127.0.0.1:9092", "Address to expose internal services")
|
||||
f.StringVar(&c.services.internal.auth.t, "services.auth.type", "None", "Auth type for internal service")
|
||||
f.StringVar(&c.services.internal.auth.token, "services.auth.token", "", "Token for internal service (when auth service is token)")
|
||||
f.StringVar(&c.services.internal.tls.keyfile, "services.tls.keyfile", "", "Path to the keyfile")
|
||||
|
||||
f.BoolVar(&c.services.external.enabled, "services.external.enabled", false, "Defines if external access is enabled")
|
||||
f.StringVar(&c.services.external.address, "services.external.address", "0.0.0.0:9093", "Address to expose external services")
|
||||
f.StringVar(&c.services.external.auth.t, "services.external.auth.type", "None", "Auth type for external service")
|
||||
f.StringVar(&c.services.external.auth.token, "services.external.auth.token", "", "Token for external service (when auth service is token)")
|
||||
f.StringVar(&c.services.external.tls.keyfile, "services.external.tls.keyfile", "", "Path to the keyfile")
|
||||
|
||||
for _, service := range c.registered {
|
||||
prefix := fmt.Sprintf("integration.%s", service.Name())
|
||||
|
@ -227,7 +240,7 @@ func (c *configuration) runWithContext(ctx context.Context, cancel context.Cance
|
|||
|
||||
healthHandler := health.Start(ctx)
|
||||
|
||||
logger.Str("address", healthHandler.Address()).Info("Health handler started")
|
||||
logger.Str("address", healthHandler.Address()).Bool("ssl", healthConfig.TLSOptions != nil).Info("Health handler started")
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
|
@ -240,7 +253,7 @@ func (c *configuration) runWithContext(ctx context.Context, cancel context.Cance
|
|||
defer wg.Done()
|
||||
s := svc.NewService(internalConfig, internalHandlers...).StartWithHealth(ctx, health)
|
||||
|
||||
logger.Str("address", s.Address()).Str("type", "internal").Info("Service handler started")
|
||||
logger.Str("address", s.Address()).Str("type", "internal").Bool("ssl", internalConfig.TLSOptions != nil).Info("Service handler started")
|
||||
|
||||
internal = s.Wait()
|
||||
|
||||
|
@ -257,7 +270,7 @@ func (c *configuration) runWithContext(ctx context.Context, cancel context.Cance
|
|||
defer wg.Done()
|
||||
s := svc.NewService(externalConfig, externalHandlers...).StartWithHealth(ctx, health)
|
||||
|
||||
logger.Str("address", s.Address()).Str("type", "external").Info("Service handler started")
|
||||
logger.Str("address", s.Address()).Str("type", "external").Bool("ssl", externalConfig.TLSOptions != nil).Info("Service handler started")
|
||||
|
||||
external = s.Wait()
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -68,6 +69,7 @@ func executeSync(t *testing.T, ctx context.Context, args ...string) error {
|
|||
cmd.SetOut(os.Stdout)
|
||||
|
||||
cmd.SetArgs(append([]string{"test"}, args...))
|
||||
logger.Info("Command: %s", strings.Join(args, " "))
|
||||
|
||||
return cmd.Execute()
|
||||
}
|
||||
|
|
39
pkg/integrations/tls.go
Normal file
39
pkg/integrations/tls.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/arangodb-helper/go-certificates"
|
||||
)
|
||||
|
||||
func tlsServerOptions(keyfile string) (*tls.Config, error) {
|
||||
cert, err := certificates.LoadKeyFile(keyfile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
ClientAuth: tls.NoClientCert,
|
||||
}, nil
|
||||
}
|
243
pkg/integrations/tls_test.go
Normal file
243
pkg/integrations/tls_test.go
Normal file
|
@ -0,0 +1,243 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
||||
ktls "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/tls"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/shutdown"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/tests/tgrpc"
|
||||
)
|
||||
|
||||
func Test_TLSCases(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
|
||||
ca1 := path.Join(directory, "CA1.keyfile")
|
||||
ca1Pem := path.Join(directory, "CA1.pem")
|
||||
server1 := path.Join(directory, "server1.keyfile")
|
||||
|
||||
ca2 := path.Join(directory, "CA2.keyfile")
|
||||
ca2Pem := path.Join(directory, "CA2.pem")
|
||||
server2 := path.Join(directory, "server2.keyfile")
|
||||
|
||||
t.Run("Arrange CA 1", func(t *testing.T) {
|
||||
caCert, caKey, err := ktls.CreateTLSCACertificate("Test Root Certificate")
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, os.WriteFile(ca1, []byte(ktls.AsKeyfile(caCert, caKey)), 0644))
|
||||
|
||||
require.NoError(t, os.WriteFile(ca1Pem, []byte(caCert), 0644))
|
||||
|
||||
serverCert, serverKey, err := ktls.CreateTLSServerCertificate(caCert, caKey, ktls.KeyfileInput{
|
||||
AltNames: []string{
|
||||
"127.0.0.1",
|
||||
},
|
||||
Email: nil,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, os.WriteFile(server1, []byte(ktls.AsKeyfile(serverCert, serverKey)), 0644))
|
||||
})
|
||||
|
||||
t.Run("Arrange CA 2", func(t *testing.T) {
|
||||
caCert, caKey, err := ktls.CreateTLSCACertificate("Test Root Certificate")
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, os.WriteFile(ca2, []byte(ktls.AsKeyfile(caCert, caKey)), 0644))
|
||||
|
||||
require.NoError(t, os.WriteFile(ca2Pem, []byte(caCert), 0644))
|
||||
|
||||
serverCert, serverKey, err := ktls.CreateTLSServerCertificate(caCert, caKey, ktls.KeyfileInput{
|
||||
AltNames: []string{
|
||||
"127.0.0.1",
|
||||
},
|
||||
Email: nil,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, os.WriteFile(server2, []byte(ktls.AsKeyfile(serverCert, serverKey)), 0644))
|
||||
})
|
||||
|
||||
c, health, internal, external := startService(t,
|
||||
"--health.tls.keyfile=",
|
||||
fmt.Sprintf("--services.tls.keyfile=%s", server1),
|
||||
fmt.Sprintf("--services.external.tls.keyfile=%s", server2),
|
||||
)
|
||||
defer c.Require(t)
|
||||
|
||||
t.Run("Without TLS", func(t *testing.T) {
|
||||
t.Run("health", func(t *testing.T) {
|
||||
require.NoError(t, executeSync(t, shutdown.Context(),
|
||||
fmt.Sprintf("--address=127.0.0.1:%d", health),
|
||||
"--tls.enabled=false",
|
||||
"client",
|
||||
"health",
|
||||
"v1"))
|
||||
})
|
||||
t.Run("internal", func(t *testing.T) {
|
||||
tgrpc.AsGRPCError(t, executeSync(t, shutdown.Context(),
|
||||
fmt.Sprintf("--address=127.0.0.1:%d", internal),
|
||||
"--tls.enabled=false",
|
||||
"client",
|
||||
"health",
|
||||
"v1")).Code(t, codes.Unavailable).Errorf(t, "connection error: desc = \"error reading server preface: EOF\"")
|
||||
})
|
||||
t.Run("external", func(t *testing.T) {
|
||||
tgrpc.AsGRPCError(t, executeSync(t, shutdown.Context(),
|
||||
fmt.Sprintf("--address=127.0.0.1:%d", external),
|
||||
"--tls.enabled=false",
|
||||
"client",
|
||||
"health",
|
||||
"v1")).Code(t, codes.Unavailable).Errorf(t, "connection error: desc = \"error reading server preface: EOF\"")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("With TLS Fallback", func(t *testing.T) {
|
||||
t.Run("health", func(t *testing.T) {
|
||||
require.NoError(t, executeSync(t, shutdown.Context(),
|
||||
fmt.Sprintf("--address=127.0.0.1:%d", health),
|
||||
"--tls.enabled=true",
|
||||
"--tls.fallback=true",
|
||||
"--tls.insecure=true",
|
||||
"client",
|
||||
"health",
|
||||
"v1"))
|
||||
})
|
||||
t.Run("internal", func(t *testing.T) {
|
||||
require.NoError(t, executeSync(t, shutdown.Context(),
|
||||
fmt.Sprintf("--address=127.0.0.1:%d", internal),
|
||||
"--tls.enabled=true",
|
||||
"--tls.insecure=true",
|
||||
"--tls.fallback=true",
|
||||
"client",
|
||||
"health",
|
||||
"v1"))
|
||||
})
|
||||
t.Run("external", func(t *testing.T) {
|
||||
require.NoError(t, executeSync(t, shutdown.Context(),
|
||||
fmt.Sprintf("--address=127.0.0.1:%d", external),
|
||||
"--tls.enabled=true",
|
||||
"--tls.insecure=true",
|
||||
"--tls.fallback=true",
|
||||
"client",
|
||||
"health",
|
||||
"v1"))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("With TLS - wrong CA", func(t *testing.T) {
|
||||
t.Run("health", func(t *testing.T) {
|
||||
tgrpc.AsGRPCError(t, executeSync(t, shutdown.Context(),
|
||||
fmt.Sprintf("--address=127.0.0.1:%d", health),
|
||||
"--tls.enabled=true",
|
||||
"client",
|
||||
"health",
|
||||
"v1")).Code(t, codes.Unavailable).Errorf(t, "connection error: desc = \"transport: authentication handshake failed: tls: first record does not look like a TLS handshake\"")
|
||||
})
|
||||
t.Run("internal", func(t *testing.T) {
|
||||
tgrpc.AsGRPCError(t, executeSync(t, shutdown.Context(),
|
||||
fmt.Sprintf("--address=127.0.0.1:%d", internal),
|
||||
"--tls.enabled=true",
|
||||
"client",
|
||||
"health",
|
||||
"v1")).Code(t, codes.Unavailable).Errorf(t, "connection error: desc = \"transport: authentication handshake failed: tls: failed to verify certificate: x509: certificate signed by unknown authority\"")
|
||||
})
|
||||
t.Run("external", func(t *testing.T) {
|
||||
tgrpc.AsGRPCError(t, executeSync(t, shutdown.Context(),
|
||||
fmt.Sprintf("--address=127.0.0.1:%d", external),
|
||||
"--tls.enabled=true",
|
||||
"client",
|
||||
"health",
|
||||
"v1")).Code(t, codes.Unavailable).Errorf(t, "connection error: desc = \"transport: authentication handshake failed: tls: failed to verify certificate: x509: certificate signed by unknown authority\"")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("With TLS - valid CA1", func(t *testing.T) {
|
||||
t.Run("health", func(t *testing.T) {
|
||||
tgrpc.AsGRPCError(t, executeSync(t, shutdown.Context(),
|
||||
fmt.Sprintf("--address=127.0.0.1:%d", health),
|
||||
"--tls.enabled=true",
|
||||
fmt.Sprintf("--tls.ca=%s", ca1Pem),
|
||||
"client",
|
||||
"health",
|
||||
"v1")).Code(t, codes.Unavailable).Errorf(t, "connection error: desc = \"transport: authentication handshake failed: tls: first record does not look like a TLS handshake\"")
|
||||
})
|
||||
t.Run("internal", func(t *testing.T) {
|
||||
require.NoError(t, executeSync(t, shutdown.Context(),
|
||||
fmt.Sprintf("--address=127.0.0.1:%d", internal),
|
||||
"--tls.enabled=true",
|
||||
fmt.Sprintf("--tls.ca=%s", ca1Pem),
|
||||
"client",
|
||||
"health",
|
||||
"v1"))
|
||||
})
|
||||
t.Run("external", func(t *testing.T) {
|
||||
tgrpc.AsGRPCError(t, executeSync(t, shutdown.Context(),
|
||||
fmt.Sprintf("--address=127.0.0.1:%d", external),
|
||||
"--tls.enabled=true",
|
||||
fmt.Sprintf("--tls.ca=%s", ca1Pem),
|
||||
"client",
|
||||
"health",
|
||||
"v1")).Code(t, codes.Unavailable).Errorf(t, "connection error: desc = \"transport: authentication handshake failed: tls: failed to verify certificate: x509: certificate signed by unknown authority (possibly because of \\\"x509: ECDSA verification failure\\\" while trying to verify candidate authority certificate \\\"Test Root Certificate\\\")\"")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("With TLS - insecure", func(t *testing.T) {
|
||||
t.Run("health", func(t *testing.T) {
|
||||
tgrpc.AsGRPCError(t, executeSync(t, shutdown.Context(),
|
||||
fmt.Sprintf("--address=127.0.0.1:%d", health),
|
||||
"--tls.enabled=true",
|
||||
"--tls.insecure=true",
|
||||
"client",
|
||||
"health",
|
||||
"v1")).Code(t, codes.Unavailable).Errorf(t, "connection error: desc = \"transport: authentication handshake failed: tls: first record does not look like a TLS handshake\"")
|
||||
})
|
||||
t.Run("internal", func(t *testing.T) {
|
||||
require.NoError(t, executeSync(t, shutdown.Context(),
|
||||
fmt.Sprintf("--address=127.0.0.1:%d", internal),
|
||||
"--tls.enabled=true",
|
||||
"--tls.insecure=true",
|
||||
"client",
|
||||
"health",
|
||||
"v1"))
|
||||
})
|
||||
t.Run("external", func(t *testing.T) {
|
||||
require.NoError(t, executeSync(t, shutdown.Context(),
|
||||
fmt.Sprintf("--address=127.0.0.1:%d", external),
|
||||
"--tls.enabled=true",
|
||||
"--tls.insecure=true",
|
||||
"client",
|
||||
"health",
|
||||
"v1"))
|
||||
})
|
||||
})
|
||||
}
|
|
@ -22,11 +22,18 @@ package util
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
pbPongV1 "github.com/arangodb/kube-arangodb/integrations/pong/v1/definition"
|
||||
pbSharedV1 "github.com/arangodb/kube-arangodb/integrations/shared/v1/definition"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/svc"
|
||||
)
|
||||
|
||||
const AuthorizationGRPCHeader = "adb-authorization"
|
||||
|
@ -40,7 +47,54 @@ func NewGRPCClient[T any](ctx context.Context, in func(cc grpc.ClientConnInterfa
|
|||
return in(con), con, nil
|
||||
}
|
||||
|
||||
func NewGRPCConn(ctx context.Context, addr string, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
|
||||
func NewOptionalTLSGRPCClient[T any](ctx context.Context, in func(cc grpc.ClientConnInterface) T, addr string, tls *tls.Config, opts ...grpc.DialOption) (T, io.Closer, error) {
|
||||
con, err := NewOptionalTLSGRPCConn(ctx, addr, tls, opts...)
|
||||
if err != nil {
|
||||
return Default[T](), nil, err
|
||||
}
|
||||
|
||||
return in(con), con, nil
|
||||
}
|
||||
|
||||
func NewOptionalTLSGRPCConn(ctx context.Context, addr string, tls *tls.Config, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
|
||||
if tls != nil {
|
||||
// Try with TLS
|
||||
tlsOpts := ClientTLS(tls)
|
||||
newOpts := make([]grpc.DialOption, len(opts)+len(tlsOpts))
|
||||
copy(newOpts, opts)
|
||||
copy(newOpts[len(opts):], tlsOpts)
|
||||
|
||||
// Create conn
|
||||
conn, err := newGRPCConn(ctx, addr, tlsOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := pbPongV1.NewPongV1Client(conn).Ping(ctx, &pbSharedV1.Empty{}); err != nil {
|
||||
if v, ok := svc.AsGRPCErrorStatus(err); !ok {
|
||||
return nil, err
|
||||
} else {
|
||||
if status := v.GRPCStatus(); status == nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if status.Code() != codes.Unavailable {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
if err := conn.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return newGRPCConn(ctx, addr, opts...)
|
||||
}
|
||||
|
||||
func newGRPCConn(ctx context.Context, addr string, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
|
||||
var z []grpc.DialOption
|
||||
|
||||
z = append(z, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
|
@ -55,6 +109,16 @@ func NewGRPCConn(ctx context.Context, addr string, opts ...grpc.DialOption) (*gr
|
|||
return conn, nil
|
||||
}
|
||||
|
||||
func NewGRPCConn(ctx context.Context, addr string, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
|
||||
return newGRPCConn(ctx, addr, opts...)
|
||||
}
|
||||
|
||||
func ClientTLS(config *tls.Config) []grpc.DialOption {
|
||||
return []grpc.DialOption{
|
||||
grpc.WithTransportCredentials(credentials.NewTLS(config)),
|
||||
}
|
||||
}
|
||||
|
||||
func TokenAuthInterceptors(token string) []grpc.DialOption {
|
||||
return []grpc.DialOption{
|
||||
grpc.WithUnaryInterceptor(func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||
|
|
94
pkg/util/k8sutil/tls/cert.go
Normal file
94
pkg/util/k8sutil/tls/cert.go
Normal file
|
@ -0,0 +1,94 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Copyright holder is ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/arangodb-helper/go-certificates"
|
||||
|
||||
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util"
|
||||
"github.com/arangodb/kube-arangodb/pkg/util/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
caTTL = time.Hour * 24 * 365 * 10 // 10 year
|
||||
tlsECDSACurve = "P256" // This curve is the default that ArangoDB accepts and plenty strong
|
||||
)
|
||||
|
||||
// CreateTLSCACertificate creates a CA certificate
|
||||
func CreateTLSCACertificate(commonName string) (string, string, error) {
|
||||
options := certificates.CreateCertificateOptions{
|
||||
CommonName: commonName,
|
||||
ValidFrom: time.Now(),
|
||||
ValidFor: caTTL,
|
||||
IsCA: true,
|
||||
ECDSACurve: tlsECDSACurve,
|
||||
}
|
||||
|
||||
cert, priv, err := certificates.CreateCertificate(options, nil)
|
||||
if err != nil {
|
||||
return "", "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return cert, priv, nil
|
||||
}
|
||||
|
||||
// CreateTLSServerCertificate creates Sever Cert in PEM Format
|
||||
func CreateTLSServerCertificate(caCert, caKey string, names KeyfileInput) (string, string, error) {
|
||||
ca, err := certificates.LoadCAFromPEM(caCert, caKey)
|
||||
if err != nil {
|
||||
return "", "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
options := certificates.CreateCertificateOptions{
|
||||
CommonName: names.AltNames[0],
|
||||
Hosts: names.AltNames,
|
||||
EmailAddresses: names.Email,
|
||||
ValidFrom: time.Now(),
|
||||
ValidFor: util.TypeOrDefault(names.TTL, api.DefaultTLSTTL.AsDuration()),
|
||||
IsCA: false,
|
||||
ECDSACurve: tlsECDSACurve,
|
||||
}
|
||||
cert, priv, err := certificates.CreateCertificate(options, &ca)
|
||||
if err != nil {
|
||||
return "", "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
return cert, priv, nil
|
||||
}
|
||||
|
||||
// CreateTLSServerKeyfile creates Sever Cert in Keyfile Format
|
||||
func CreateTLSServerKeyfile(caCert, caKey string, names KeyfileInput) (string, error) {
|
||||
cert, priv, err := CreateTLSServerCertificate(caCert, caKey, names)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return AsKeyfile(cert, priv), nil
|
||||
}
|
||||
|
||||
func AsKeyfile(cert, priv string) string {
|
||||
return strings.TrimSpace(cert) + "\n" +
|
||||
strings.TrimSpace(priv)
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
//
|
||||
// DISCLAIMER
|
||||
//
|
||||
// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
|
||||
// Copyright 2016-2024 ArangoDB GmbH, Cologne, Germany
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
@ -22,6 +22,7 @@ package tls
|
|||
|
||||
import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
core "k8s.io/api/core/v1"
|
||||
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -33,6 +34,7 @@ import (
|
|||
)
|
||||
|
||||
type KeyfileInput struct {
|
||||
TTL *time.Duration
|
||||
AltNames []string
|
||||
Email []string
|
||||
}
|
||||
|
|
|
@ -20,10 +20,32 @@
|
|||
|
||||
package svc
|
||||
|
||||
import "google.golang.org/grpc"
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
type Configuration struct {
|
||||
Address string
|
||||
|
||||
TLSOptions *tls.Config
|
||||
|
||||
Options []grpc.ServerOption
|
||||
}
|
||||
|
||||
func (c *Configuration) RenderOptions() []grpc.ServerOption {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ret := make([]grpc.ServerOption, len(c.Options))
|
||||
copy(ret, c.Options)
|
||||
|
||||
if tls := c.TLSOptions; tls != nil {
|
||||
ret = append(ret, grpc.Creds(credentials.NewTLS(tls)))
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ func newService(cfg Configuration, handlers ...Handler) *service {
|
|||
var q service
|
||||
|
||||
q.cfg = cfg
|
||||
q.server = grpc.NewServer(cfg.Options...)
|
||||
q.server = grpc.NewServer(cfg.RenderOptions()...)
|
||||
q.handlers = handlers
|
||||
|
||||
for _, handler := range q.handlers {
|
||||
|
|
|
@ -46,13 +46,13 @@ func WaitForAddress(t *testing.T, addr string, port int) {
|
|||
tickerT := time.NewTicker(125 * time.Millisecond)
|
||||
defer tickerT.Stop()
|
||||
|
||||
timerT := time.NewTimer(1 * time.Second)
|
||||
timerT := time.NewTimer(5 * time.Second)
|
||||
defer timerT.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-timerT.C:
|
||||
require.Fail(t, "Timeouted")
|
||||
require.Fail(t, "Timeouted", addr, port)
|
||||
case <-tickerT.C:
|
||||
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", addr, port), 125*time.Millisecond)
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in a new issue