1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-23 07:19:41 +02:00

fix(kube): Use KubeClusterAccessService for Helm operations [EE-2500] (#6559)

This commit is contained in:
Marcelo Rydel 2022-03-21 09:51:29 -03:00 committed by GitHub
parent cf7746082b
commit c486130a9f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 105 additions and 103 deletions

View file

@ -7,26 +7,27 @@ import (
"fmt"
"io/ioutil"
"log"
"strings"
"github.com/pkg/errors"
portainer "github.com/portainer/portainer/api"
)
// KubeConfigService represents a service that is responsible for handling kubeconfig operations
type KubeConfigService interface {
// KubeClusterAccessService represents a service that is responsible for centralizing kube cluster access data
type KubeClusterAccessService interface {
IsSecure() bool
GetKubeConfigInternal(endpointId portainer.EndpointID, authToken string) kubernetesClusterAccess
GetData(hostURL string, endpointId portainer.EndpointID) kubernetesClusterAccessData
}
// KubernetesClusterAccess represents core details which can be used to generate KubeConfig file/data
type kubernetesClusterAccess struct {
type kubernetesClusterAccessData struct {
ClusterServerURL string `example:"https://mycompany.k8s.com"`
CertificateAuthorityFile string `example:"/data/tls/localhost.crt"`
CertificateAuthorityData string `example:"MIIC5TCCAc2gAwIBAgIJAJ+...+xuhOaFXwQ=="`
AuthToken string `example:"ey..."`
}
type kubeConfigCAService struct {
type kubeClusterAccessService struct {
baseURL string
httpsBindAddr string
certificateAuthorityFile string
certificateAuthorityData string
@ -39,14 +40,15 @@ var (
errTLSCertValidation = errors.New("failed to parse tls certificate")
)
// NewKubeConfigCAService encapsulates generation of core KubeConfig data
func NewKubeConfigCAService(httpsBindAddr string, tlsCertPath string) KubeConfigService {
// NewKubeClusterAccessService creates a new instance of a KubeClusterAccessService
func NewKubeClusterAccessService(baseURL, httpsBindAddr, tlsCertPath string) KubeClusterAccessService {
certificateAuthorityData, err := getCertificateAuthorityData(tlsCertPath)
if err != nil {
log.Printf("[DEBUG] [internal,kubeconfig] [message: %s, generated KubeConfig will be insecure]", err.Error())
}
return &kubeConfigCAService{
return &kubeClusterAccessService{
baseURL: baseURL,
httpsBindAddr: httpsBindAddr,
certificateAuthorityFile: tlsCertPath,
certificateAuthorityData: certificateAuthorityData,
@ -82,23 +84,27 @@ func getCertificateAuthorityData(tlsCertPath string) (string, error) {
// this is based on the fact that we can successfully extract `certificateAuthorityData` from
// certificate file at `tlsCertPath`. If we can successfully extract `certificateAuthorityData`,
// then this will be used as `certificate-authority-data` attribute in a generated KubeConfig.
func (kccas *kubeConfigCAService) IsSecure() bool {
return kccas.certificateAuthorityData != ""
func (service *kubeClusterAccessService) IsSecure() bool {
return service.certificateAuthorityData != ""
}
// GetKubeConfigInternal returns K8s cluster access details for the specified environment(endpoint).
// On startup, portainer generates a certificate against localhost at specified `httpsBindAddr` port, hence
// the kubeconfig generated should only be utilised by internal portainer binaries as the `ClusterServerURL`
// points to the internally accessible `https` based `localhost` address.
// GetData returns K8s cluster access details for the specified environment(endpoint).
// The struct can be used to:
// - generate a kubeconfig file
// - pass down params to binaries
func (kccas *kubeConfigCAService) GetKubeConfigInternal(endpointId portainer.EndpointID, authToken string) kubernetesClusterAccess {
clusterServerUrl := fmt.Sprintf("https://localhost%s/api/endpoints/%s/kubernetes", kccas.httpsBindAddr, fmt.Sprint(endpointId))
return kubernetesClusterAccess{
ClusterServerURL: clusterServerUrl,
CertificateAuthorityFile: kccas.certificateAuthorityFile,
CertificateAuthorityData: kccas.certificateAuthorityData,
AuthToken: authToken,
func (service *kubeClusterAccessService) GetData(hostURL string, endpointID portainer.EndpointID) kubernetesClusterAccessData {
baseURL := service.baseURL
if baseURL != "/" {
baseURL = fmt.Sprintf("/%s/", strings.Trim(baseURL, "/"))
}
clusterURL := hostURL + service.httpsBindAddr + baseURL
clusterServerURL := fmt.Sprintf("https://%sapi/endpoints/%d/kubernetes", clusterURL, endpointID)
return kubernetesClusterAccessData{
ClusterServerURL: clusterServerURL,
CertificateAuthorityFile: service.certificateAuthorityFile,
CertificateAuthorityData: service.certificateAuthorityData,
}
}

View file

@ -78,11 +78,11 @@ func Test_getCertificateAuthorityData(t *testing.T) {
})
}
func TestKubeConfigService_IsSecure(t *testing.T) {
func TestKubeClusterAccessService_IsSecure(t *testing.T) {
is := assert.New(t)
t.Run("IsSecure should be false", func(t *testing.T) {
kcs := NewKubeConfigCAService("", "")
kcs := NewKubeClusterAccessService("", "", "")
is.False(kcs.IsSecure(), "should be false if TLS cert not provided")
})
@ -90,39 +90,32 @@ func TestKubeConfigService_IsSecure(t *testing.T) {
filePath, teardown := createTempFile("valid-cert.crt", certData)
defer teardown()
kcs := NewKubeConfigCAService("", filePath)
kcs := NewKubeClusterAccessService("", "", filePath)
is.True(kcs.IsSecure(), "should be true if valid TLS cert (path and content) provided")
})
}
func TestKubeConfigService_GetKubeConfigInternal(t *testing.T) {
func TestKubeClusterAccessService_GetKubeConfigInternal(t *testing.T) {
is := assert.New(t)
t.Run("GetKubeConfigInternal returns localhost address", func(t *testing.T) {
kcs := NewKubeConfigCAService("", "")
clusterAccessDetails := kcs.GetKubeConfigInternal(1, "some-token")
is.True(strings.Contains(clusterAccessDetails.ClusterServerURL, "https://localhost"), "should contain localhost address")
t.Run("GetData contains host address", func(t *testing.T) {
kcs := NewKubeClusterAccessService("/", "", "")
clusterAccessDetails := kcs.GetData("mysite.com", 1)
is.True(strings.Contains(clusterAccessDetails.ClusterServerURL, "https://mysite.com"), "should contain host address")
})
t.Run("GetKubeConfigInternal contains https bind address port", func(t *testing.T) {
kcs := NewKubeConfigCAService(":1010", "")
clusterAccessDetails := kcs.GetKubeConfigInternal(1, "some-token")
is.True(strings.Contains(clusterAccessDetails.ClusterServerURL, ":1010"), "should contain bind address port")
})
t.Run("GetKubeConfigInternal contains environment proxy url", func(t *testing.T) {
kcs := NewKubeConfigCAService("", "")
clusterAccessDetails := kcs.GetKubeConfigInternal(100, "some-token")
t.Run("GetData contains environment proxy url", func(t *testing.T) {
kcs := NewKubeClusterAccessService("/", "", "")
clusterAccessDetails := kcs.GetData("mysite.com", 100)
is.True(strings.Contains(clusterAccessDetails.ClusterServerURL, "api/endpoints/100/kubernetes"), "should contain environment proxy url")
})
t.Run("GetKubeConfigInternal returns insecure cluster access config", func(t *testing.T) {
kcs := NewKubeConfigCAService("", "")
clusterAccessDetails := kcs.GetKubeConfigInternal(1, "some-token")
t.Run("GetData returns insecure cluster access config", func(t *testing.T) {
kcs := NewKubeClusterAccessService("/", ":9443", "")
clusterAccessDetails := kcs.GetData("mysite.com", 1)
wantClusterAccessDetails := kubernetesClusterAccess{
ClusterServerURL: "https://localhost/api/endpoints/1/kubernetes",
AuthToken: "some-token",
wantClusterAccessDetails := kubernetesClusterAccessData{
ClusterServerURL: "https://mysite.com:9443/api/endpoints/1/kubernetes",
CertificateAuthorityFile: "",
CertificateAuthorityData: "",
}
@ -130,16 +123,15 @@ func TestKubeConfigService_GetKubeConfigInternal(t *testing.T) {
is.Equal(clusterAccessDetails, wantClusterAccessDetails)
})
t.Run("GetKubeConfigInternal returns secure cluster access config", func(t *testing.T) {
t.Run("GetData returns secure cluster access config", func(t *testing.T) {
filePath, teardown := createTempFile("valid-cert.crt", certData)
defer teardown()
kcs := NewKubeConfigCAService("", filePath)
clusterAccessDetails := kcs.GetKubeConfigInternal(1, "some-token")
kcs := NewKubeClusterAccessService("/", "", filePath)
clusterAccessDetails := kcs.GetData("localhost", 1)
wantClusterAccessDetails := kubernetesClusterAccess{
wantClusterAccessDetails := kubernetesClusterAccessData{
ClusterServerURL: "https://localhost/api/endpoints/1/kubernetes",
AuthToken: "some-token",
CertificateAuthorityFile: filePath,
CertificateAuthorityData: certDataString,
}