mirror of
https://github.com/outbackdingo/proxmox-cloud-controller-manager.git
synced 2026-01-27 10:20:13 +00:00
feat: add config options token_id_file & token_secret_file
Adds additional config options to read proxmox-cluster credentials from separate files. Signed-off-by: 3deep5me <manuel.karim5@gmail.com>
This commit is contained in:
@@ -16,7 +16,7 @@ maintainers:
|
|||||||
# This is the chart version. This version number should be incremented each time you make changes
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
# to the chart and its templates, including the app version.
|
# to the chart and its templates, including the app version.
|
||||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
version: 0.2.15
|
version: 0.2.16
|
||||||
# This is the version number of the application being deployed. This version number should be
|
# This is the version number of the application being deployed. This version number should be
|
||||||
# incremented each time you make changes to the application. Versions are not expected to
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
|
|||||||
@@ -68,6 +68,56 @@ tolerations:
|
|||||||
effect: NoSchedule
|
effect: NoSchedule
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Example for credentials from seperate Secrets
|
||||||
|
```yaml
|
||||||
|
# helm-values.yaml
|
||||||
|
config:
|
||||||
|
clusters:
|
||||||
|
- url: https://cluster-api-1.exmple.com:8006/api2/json
|
||||||
|
insecure: false
|
||||||
|
token_id_file: /run/secrets/cluster-1/token_id
|
||||||
|
token_secret_file: /run/secrets/cluster-1/token_secret
|
||||||
|
region: cluster-1
|
||||||
|
- url: https://cluster-api-2.exmple.com:8006/api2/json
|
||||||
|
insecure: false
|
||||||
|
token_id_file: /run/secrets/cluster-2/token_id
|
||||||
|
token_secret_file: /run/secrets/cluster-2/token_secret
|
||||||
|
region: cluster-2
|
||||||
|
extraVolumes:
|
||||||
|
- name: credentials-cluster-1
|
||||||
|
secret:
|
||||||
|
secretName: proxmox-credentials-cluster-1
|
||||||
|
- name: credentials-cluster-2
|
||||||
|
secret:
|
||||||
|
secretName: proxmox-credentials-cluster-2
|
||||||
|
extraVolumeMounts:
|
||||||
|
- name: credentials-cluster-1
|
||||||
|
readOnly: true
|
||||||
|
mountPath: "/run/secrets/cluster-1"
|
||||||
|
- name: credentials-cluster-2
|
||||||
|
readOnly: true
|
||||||
|
mountPath: "/run/secrets/cluster-2"
|
||||||
|
|
||||||
|
```
|
||||||
|
```yaml
|
||||||
|
# secrets-proxmox-clusters.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: proxmox-credentials-cluster-1
|
||||||
|
stringData:
|
||||||
|
token_id: kubernetes@pve!csi
|
||||||
|
token_secret: key1
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: proxmox-credentials-cluster-2
|
||||||
|
stringData:
|
||||||
|
token_id: kubernetes@pve!csi
|
||||||
|
token_secret: key2
|
||||||
|
```
|
||||||
|
|
||||||
Deploy chart:
|
Deploy chart:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|||||||
@@ -66,6 +66,56 @@ tolerations:
|
|||||||
effect: NoSchedule
|
effect: NoSchedule
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Example for credentials from separate Secrets
|
||||||
|
```yaml
|
||||||
|
# helm-values.yaml
|
||||||
|
config:
|
||||||
|
clusters:
|
||||||
|
- url: https://cluster-api-1.exmple.com:8006/api2/json
|
||||||
|
insecure: false
|
||||||
|
token_id_file: /run/secrets/cluster-1/token_id
|
||||||
|
token_secret_file: /run/secrets/cluster-1/token_secret
|
||||||
|
region: cluster-1
|
||||||
|
- url: https://cluster-api-2.exmple.com:8006/api2/json
|
||||||
|
insecure: false
|
||||||
|
token_id_file: /run/secrets/cluster-2/token_id
|
||||||
|
token_secret_file: /run/secrets/cluster-2/token_secret
|
||||||
|
region: cluster-2
|
||||||
|
extraVolumes:
|
||||||
|
- name: credentials-cluster-1
|
||||||
|
secret:
|
||||||
|
secretName: proxmox-credentials-cluster-1
|
||||||
|
- name: credentials-cluster-2
|
||||||
|
secret:
|
||||||
|
secretName: proxmox-credentials-cluster-2
|
||||||
|
extraVolumeMounts:
|
||||||
|
- name: credentials-cluster-1
|
||||||
|
readOnly: true
|
||||||
|
mountPath: "/run/secrets/cluster-1"
|
||||||
|
- name: credentials-cluster-2
|
||||||
|
readOnly: true
|
||||||
|
mountPath: "/run/secrets/cluster-2"
|
||||||
|
|
||||||
|
```
|
||||||
|
```yaml
|
||||||
|
# secrets-proxmox-clusters.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: proxmox-credentials-cluster-1
|
||||||
|
stringData:
|
||||||
|
token_id: kubernetes@pve!csi
|
||||||
|
token_secret: key1
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: proxmox-credentials-cluster-2
|
||||||
|
stringData:
|
||||||
|
token_id: kubernetes@pve!csi
|
||||||
|
token_secret: key2
|
||||||
|
```
|
||||||
|
|
||||||
Deploy chart:
|
Deploy chart:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ clusters:
|
|||||||
# Proxmox api token
|
# Proxmox api token
|
||||||
token_id: "kubernetes-csi@pve!csi"
|
token_id: "kubernetes-csi@pve!csi"
|
||||||
token_secret: "secret"
|
token_secret: "secret"
|
||||||
|
# (optional) Proxmox api token from separate file (s. Helm README.md)
|
||||||
|
# token_id_file: /run/secrets/region-1/token_id
|
||||||
|
# token_secret_file: /run/secrets/region-1/token_secret
|
||||||
# Region name, which is cluster name
|
# Region name, which is cluster name
|
||||||
region: Region-1
|
region: Region-1
|
||||||
|
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ type ClustersConfig struct {
|
|||||||
var (
|
var (
|
||||||
ErrMissingPVERegion = errors.New("missing PVE region in cloud config")
|
ErrMissingPVERegion = errors.New("missing PVE region in cloud config")
|
||||||
ErrMissingPVEAPIURL = errors.New("missing PVE API URL in cloud config")
|
ErrMissingPVEAPIURL = errors.New("missing PVE API URL in cloud config")
|
||||||
ErrAuthCredentialsMissing = errors.New("user or token credentials are required")
|
ErrAuthCredentialsMissing = errors.New("user, token or file credentials are required")
|
||||||
ErrInvalidAuthCredentials = errors.New("must specify one of user or token credentials, not both")
|
ErrInvalidAuthCredentials = errors.New("must specify one of user, token or file credentials, not multiple")
|
||||||
ErrInvalidCloudConfig = errors.New("invalid cloud config")
|
ErrInvalidCloudConfig = errors.New("invalid cloud config")
|
||||||
ErrInvalidNetworkMode = fmt.Errorf("invalid network mode, valid modes are %v", ValidNetworkModes)
|
ErrInvalidNetworkMode = fmt.Errorf("invalid network mode, valid modes are %v", ValidNetworkModes)
|
||||||
)
|
)
|
||||||
@@ -93,11 +93,15 @@ func ReadCloudConfig(config io.Reader) (ClustersConfig, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for idx, c := range cfg.Clusters {
|
for idx, c := range cfg.Clusters {
|
||||||
if c.Username != "" && c.Password != "" {
|
hasTokenAuth := c.TokenID != "" || c.TokenSecret != ""
|
||||||
if c.TokenID != "" || c.TokenSecret != "" {
|
hasTokenFileAuth := c.TokenIDFile != "" || c.TokenSecretFile != ""
|
||||||
return ClustersConfig{}, fmt.Errorf("cluster #%d: %w", idx+1, ErrInvalidAuthCredentials)
|
|
||||||
}
|
hasUserAuth := c.Username != "" && c.Password != ""
|
||||||
} else if c.TokenID == "" || c.TokenSecret == "" {
|
if (hasTokenAuth && hasUserAuth) || (hasTokenFileAuth && hasUserAuth) || (hasTokenAuth && hasTokenFileAuth) {
|
||||||
|
return ClustersConfig{}, fmt.Errorf("cluster #%d: %w", idx+1, ErrInvalidAuthCredentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasTokenAuth && !hasTokenFileAuth && !hasUserAuth {
|
||||||
return ClustersConfig{}, fmt.Errorf("cluster #%d: %w", idx+1, ErrAuthCredentialsMissing)
|
return ClustersConfig{}, fmt.Errorf("cluster #%d: %w", idx+1, ErrAuthCredentialsMissing)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,20 @@ clusters:
|
|||||||
assert.ErrorIs(t, err, providerconfig.ErrAuthCredentialsMissing)
|
assert.ErrorIs(t, err, providerconfig.ErrAuthCredentialsMissing)
|
||||||
assert.NotNil(t, cfg)
|
assert.NotNil(t, cfg)
|
||||||
|
|
||||||
|
// Valid config with one cluster and secret_file
|
||||||
|
cfg, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
||||||
|
clusters:
|
||||||
|
- url: https://example.com
|
||||||
|
insecure: false
|
||||||
|
token_id_file: "/etc/proxmox-secrets/cluster1/token_id"
|
||||||
|
token_secret_file: "/etc/proxmox-secrets/cluster1/token_secret"
|
||||||
|
region: cluster-1
|
||||||
|
`))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.NotNil(t, cfg)
|
||||||
|
assert.Equal(t, 1, len(cfg.Clusters))
|
||||||
|
assert.Equal(t, "/etc/proxmox-secrets/cluster1/token_id", cfg.Clusters[0].TokenIDFile)
|
||||||
|
|
||||||
// Valid config with one cluster
|
// Valid config with one cluster
|
||||||
cfg, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
cfg, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
||||||
clusters:
|
clusters:
|
||||||
@@ -118,6 +132,21 @@ clusters:
|
|||||||
assert.Equal(t, 1, len(cfg.Clusters))
|
assert.Equal(t, 1, len(cfg.Clusters))
|
||||||
assert.Equal(t, providerconfig.ProviderCapmox, cfg.Features.Provider)
|
assert.Equal(t, providerconfig.ProviderCapmox, cfg.Features.Provider)
|
||||||
|
|
||||||
|
// Errors when token_id/token_secret are set with token_id_file/token_secret_file
|
||||||
|
_, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
||||||
|
features:
|
||||||
|
provider: 'capmox'
|
||||||
|
clusters:
|
||||||
|
- url: https://example.com
|
||||||
|
insecure: false
|
||||||
|
token_id_file: "/etc/proxmox-secrets/cluster1/token_id"
|
||||||
|
token_secret_file: "/etc/proxmox-secrets/cluster1/token_secret"
|
||||||
|
token_id: "ha"
|
||||||
|
token_secret: "secret"
|
||||||
|
region: cluster-1
|
||||||
|
`))
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
// Errors when username/password are set with token_id/token_secret
|
// Errors when username/password are set with token_id/token_secret
|
||||||
_, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
_, err = providerconfig.ReadCloudConfig(strings.NewReader(`
|
||||||
features:
|
features:
|
||||||
|
|||||||
@@ -35,13 +35,15 @@ import (
|
|||||||
|
|
||||||
// ProxmoxCluster defines a Proxmox cluster configuration.
|
// ProxmoxCluster defines a Proxmox cluster configuration.
|
||||||
type ProxmoxCluster struct {
|
type ProxmoxCluster struct {
|
||||||
URL string `yaml:"url"`
|
URL string `yaml:"url"`
|
||||||
Insecure bool `yaml:"insecure,omitempty"`
|
Insecure bool `yaml:"insecure,omitempty"`
|
||||||
TokenID string `yaml:"token_id,omitempty"`
|
TokenIDFile string `yaml:"token_id_file,omitempty"`
|
||||||
TokenSecret string `yaml:"token_secret,omitempty"`
|
TokenSecretFile string `yaml:"token_secret_file,omitempty"`
|
||||||
Username string `yaml:"username,omitempty"`
|
TokenID string `yaml:"token_id,omitempty"`
|
||||||
Password string `yaml:"password,omitempty"`
|
TokenSecret string `yaml:"token_secret,omitempty"`
|
||||||
Region string `yaml:"region,omitempty"`
|
Username string `yaml:"username,omitempty"`
|
||||||
|
Password string `yaml:"password,omitempty"`
|
||||||
|
Region string `yaml:"region,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProxmoxPool is a Proxmox client.
|
// ProxmoxPool is a Proxmox client.
|
||||||
@@ -56,6 +58,24 @@ func NewProxmoxPool(config []*ProxmoxCluster, hClient *http.Client) (*ProxmoxPoo
|
|||||||
clients := make(map[string]*proxmox.Client, clusters)
|
clients := make(map[string]*proxmox.Client, clusters)
|
||||||
|
|
||||||
for _, cfg := range config {
|
for _, cfg := range config {
|
||||||
|
if cfg.TokenID == "" {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
cfg.TokenID, err = readValueFromFile(cfg.TokenIDFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.TokenSecret == "" {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
cfg.TokenSecret, err = readValueFromFile(cfg.TokenSecretFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tlsconf := &tls.Config{InsecureSkipVerify: true}
|
tlsconf := &tls.Config{InsecureSkipVerify: true}
|
||||||
if !cfg.Insecure {
|
if !cfg.Insecure {
|
||||||
tlsconf = nil
|
tlsconf = nil
|
||||||
@@ -261,3 +281,16 @@ func (c *ProxmoxPool) getSMBSetting(vmInfo map[string]interface{}, name string)
|
|||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readValueFromFile(path string) (string, error) {
|
||||||
|
if path == "" {
|
||||||
|
return "", fmt.Errorf("path cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read file '%s': %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimSpace(string(content)), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package proxmoxpool_test
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/jarcoal/httpmock"
|
"github.com/jarcoal/httpmock"
|
||||||
@@ -28,7 +29,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func newClusterEnv() []*pxpool.ProxmoxCluster {
|
func newClusterEnv() []*pxpool.ProxmoxCluster {
|
||||||
// copilot convert the cfg call to an array of []*proxmox_pool.ProxmoxCluster:
|
|
||||||
cfg := []*pxpool.ProxmoxCluster{
|
cfg := []*pxpool.ProxmoxCluster{
|
||||||
{
|
{
|
||||||
URL: "https://127.0.0.1:8006/api2/json",
|
URL: "https://127.0.0.1:8006/api2/json",
|
||||||
@@ -49,6 +49,20 @@ func newClusterEnv() []*pxpool.ProxmoxCluster {
|
|||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newClusterEnvWithFiles(tokenIDPath, tokenSecretPath string) []*pxpool.ProxmoxCluster {
|
||||||
|
cfg := []*pxpool.ProxmoxCluster{
|
||||||
|
{
|
||||||
|
URL: "https://127.0.0.1:8006/api2/json",
|
||||||
|
Insecure: false,
|
||||||
|
TokenIDFile: tokenIDPath,
|
||||||
|
TokenSecretFile: tokenSecretPath,
|
||||||
|
Region: "cluster-1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewClient(t *testing.T) {
|
func TestNewClient(t *testing.T) {
|
||||||
cfg := newClusterEnv()
|
cfg := newClusterEnv()
|
||||||
assert.NotNil(t, cfg)
|
assert.NotNil(t, cfg)
|
||||||
@@ -62,6 +76,29 @@ func TestNewClient(t *testing.T) {
|
|||||||
assert.NotNil(t, pClient)
|
assert.NotNil(t, pClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewClientWithCredentialsFromFile(t *testing.T) {
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
|
||||||
|
tokenIDFile, err := os.CreateTemp(tempDir, "token_id")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
tokenSecretFile, err := os.CreateTemp(tempDir, "token_secret")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
_, err = tokenIDFile.WriteString("user!token-id")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
_, err = tokenSecretFile.WriteString("secret")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
cfg := newClusterEnvWithFiles(tokenIDFile.Name(), tokenSecretFile.Name())
|
||||||
|
|
||||||
|
pClient, err := pxpool.NewProxmoxPool(cfg, nil)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.NotNil(t, pClient)
|
||||||
|
assert.Equal(t, "user!token-id", cfg[0].TokenID)
|
||||||
|
assert.Equal(t, "secret", cfg[0].TokenSecret)
|
||||||
|
}
|
||||||
|
|
||||||
func TestCheckClusters(t *testing.T) {
|
func TestCheckClusters(t *testing.T) {
|
||||||
cfg := newClusterEnv()
|
cfg := newClusterEnv()
|
||||||
assert.NotNil(t, cfg)
|
assert.NotNil(t, cfg)
|
||||||
|
|||||||
Reference in New Issue
Block a user