2022-06-01 20:44:45 +02:00
package kubernetes
import (
"encoding/base64"
"fmt"
"strings"
"gopkg.in/yaml.v2"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/proxy"
)
// apiConfig contains config for API server
type apiConfig struct {
aw * apiWatcher
}
// Config represent configuration file for kubernetes API server connection
// https://github.com/kubernetes/client-go/blob/master/tools/clientcmd/api/v1/types.go#L28
type Config struct {
Kind string ` yaml:"kind,omitempty" `
APIVersion string ` yaml:"apiVersion,omitempty" `
Clusters [ ] struct {
Name string ` yaml:"name" `
Cluster * Cluster ` yaml:"cluster" `
} ` yaml:"clusters" `
AuthInfos [ ] struct {
Name string ` yaml:"name" `
AuthInfo * AuthInfo ` yaml:"user" `
} ` yaml:"users" `
Contexts [ ] struct {
Name string ` yaml:"name" `
Context * Context ` yaml:"context" `
} ` yaml:"contexts" `
CurrentContext string ` yaml:"current-context" `
}
// Cluster contains information about how to communicate with a kubernetes cluster
type Cluster struct {
Server string ` yaml:"server" `
TLSServerName string ` yaml:"tls-server-name,omitempty" `
InsecureSkipTLSVerify bool ` yaml:"insecure-skip-tls-verify,omitempty" `
CertificateAuthority string ` yaml:"certificate-authority,omitempty" `
CertificateAuthorityData string ` yaml:"certificate-authority-data,omitempty" `
ProxyURL * proxy . URL ` yaml:"proxy-url,omitempty" `
}
// AuthInfo contains information that describes identity information. This is use to tell the kubernetes cluster who you are.
type AuthInfo struct {
ClientCertificate string ` yaml:"client-certificate,omitempty" `
ClientCertificateData string ` yaml:"client-certificate-data,omitempty" `
ClientKey string ` yaml:"client-key,omitempty" `
ClientKeyData string ` yaml:"client-key-data,omitempty" `
// TODO add support for it
Exec * ExecConfig ` yaml:"exec,omitempty" `
Token string ` yaml:"token,omitempty" `
TokenFile string ` yaml:"tokenFile,omitempty" `
Impersonate string ` yaml:"act-as,omitempty" `
ImpersonateUID string ` yaml:"act-as-uid,omitempty" `
ImpersonateGroups [ ] string ` yaml:"act-as-groups,omitempty" `
ImpersonateUserExtra [ ] string ` yaml:"act-as-user-extra,omitempty" `
Username string ` yaml:"username,omitempty" `
Password string ` yaml:"password,omitempty" `
}
func ( au * AuthInfo ) validate ( ) error {
2022-06-06 13:24:52 +02:00
errContext := "field %q is not supported yet; if you feel it is needed please open a feature request at https://github.com/VictoriaMetrics/VictoriaMetrics/issues/new"
2022-06-01 20:44:45 +02:00
if au . Exec != nil {
return fmt . Errorf ( errContext , "exec" )
}
if len ( au . ImpersonateUID ) > 0 {
return fmt . Errorf ( errContext , "act-as-uid" )
}
if len ( au . Impersonate ) > 0 {
return fmt . Errorf ( errContext , "act-as" )
}
if len ( au . ImpersonateGroups ) > 0 {
return fmt . Errorf ( errContext , "act-as-groups" )
}
if len ( au . ImpersonateUserExtra ) > 0 {
return fmt . Errorf ( errContext , "act-as-user-extra" )
}
if len ( au . Password ) > 0 && len ( au . Username ) == 0 {
return fmt . Errorf ( "username cannot be empty, if password defined" )
}
return nil
}
// ExecConfig contains information about os.command, that returns auth token for kubernetes cluster connection
type ExecConfig struct {
// Command to execute.
Command string ` json:"command" `
// Arguments to pass to the command when executing it.
Args [ ] string ` json:"args" `
// Env defines additional environment variables to expose to the process. These
// are unioned with the host's environment, as well as variables client-go uses
// to pass argument to the plugin.
Env [ ] ExecEnvVar ` json:"env" `
// Preferred input version of the ExecInfo. The returned ExecCredentials MUST use
// the same encoding version as the input.
APIVersion string ` json:"apiVersion,omitempty" `
// This text is shown to the user when the executable doesn't seem to be
// present. For example, `brew install foo-cli` might be a good InstallHint for
// foo-cli on Mac OS systems.
InstallHint string ` json:"installHint,omitempty" `
// ProvideClusterInfo determines whether or not to provide cluster information,
// which could potentially contain very large CA data, to this exec plugin as a
// part of the KUBERNETES_EXEC_INFO environment variable. By default, it is set
// to false. Package k8s.io/client-go/tools/auth/exec provides helper methods for
// reading this environment variable.
ProvideClusterInfo bool ` json:"provideClusterInfo" `
// InteractiveMode determines this plugin's relationship with standard input. Valid
// values are "Never" (this exec plugin never uses standard input), "IfAvailable" (this
// exec plugin wants to use standard input if it is available), or "Always" (this exec
// plugin requires standard input to function). See ExecInteractiveMode values for more
// details.
//
// If APIVersion is client.authentication.k8s.io/v1alpha1 or
// client.authentication.k8s.io/v1beta1, then this field is optional and defaults
// to "IfAvailable" when unset. Otherwise, this field is required.
//+optional
InteractiveMode string ` json:"interactiveMode,omitempty" `
}
// ExecEnvVar is used for setting environment variables when executing an exec-based
// credential plugin.
type ExecEnvVar struct {
Name string ` json:"name" `
Value string ` json:"value" `
}
// Context is a tuple of references to a cluster and AuthInfo
type Context struct {
Cluster string ` yaml:"cluster" `
AuthInfo string ` yaml:"user" `
}
type kubeConfig struct {
basicAuth * promauth . BasicAuthConfig
server string
token string
tokenFile string
tlsConfig * promauth . TLSConfig
proxyURL * proxy . URL
}
2022-06-06 13:24:52 +02:00
func newKubeConfig ( kubeConfigFile string ) ( * kubeConfig , error ) {
data , err := fs . ReadFileOrHTTP ( kubeConfigFile )
2022-06-01 20:44:45 +02:00
if err != nil {
2022-06-06 13:24:52 +02:00
return nil , fmt . Errorf ( "cannot read %q: %w" , kubeConfigFile , err )
}
var cfg Config
if err = yaml . Unmarshal ( data , & cfg ) ; err != nil {
return nil , fmt . Errorf ( "cannot parse %q: %w" , kubeConfigFile , err )
2022-06-01 20:44:45 +02:00
}
2022-06-06 13:24:52 +02:00
kc , err := cfg . buildKubeConfig ( )
if err != nil {
return nil , fmt . Errorf ( "cannot build kubeConfig from %q: %w" , kubeConfigFile , err )
2022-06-01 20:44:45 +02:00
}
2022-06-06 13:24:52 +02:00
return kc , nil
}
2022-06-01 20:44:45 +02:00
2022-06-06 13:24:52 +02:00
func ( cfg * Config ) buildKubeConfig ( ) ( * kubeConfig , error ) {
2022-06-01 20:44:45 +02:00
authInfos := make ( map [ string ] * AuthInfo )
2022-06-06 13:24:52 +02:00
for _ , obj := range cfg . AuthInfos {
2022-06-01 20:44:45 +02:00
authInfos [ obj . Name ] = obj . AuthInfo
}
clusterInfos := make ( map [ string ] * Cluster )
2022-06-06 13:24:52 +02:00
for _ , obj := range cfg . Clusters {
2022-06-01 20:44:45 +02:00
clusterInfos [ obj . Name ] = obj . Cluster
}
contexts := make ( map [ string ] * Context )
2022-06-06 13:24:52 +02:00
for _ , obj := range cfg . Contexts {
2022-06-01 20:44:45 +02:00
contexts [ obj . Name ] = obj . Context
}
2022-06-06 13:24:52 +02:00
contextName := cfg . CurrentContext
2022-06-01 20:44:45 +02:00
configContext := contexts [ contextName ]
if configContext == nil {
2022-06-06 13:24:52 +02:00
return nil , fmt . Errorf ( "missing context %q" , contextName )
2022-06-01 20:44:45 +02:00
}
clusterInfoName := configContext . Cluster
configClusterInfo := clusterInfos [ clusterInfoName ]
if configClusterInfo == nil {
2022-06-06 13:24:52 +02:00
return nil , fmt . Errorf ( "missing cluster config %q at context %q" , clusterInfoName , contextName )
2022-06-01 20:44:45 +02:00
}
2022-06-06 13:24:52 +02:00
server := configClusterInfo . Server
if len ( server ) == 0 {
return nil , fmt . Errorf ( "missing kubernetes server address for config %q at context %q" , clusterInfoName , contextName )
2022-06-01 20:44:45 +02:00
}
authInfoName := configContext . AuthInfo
configAuthInfo := authInfos [ authInfoName ]
if authInfoName != "" && configAuthInfo == nil {
2022-06-06 13:24:52 +02:00
return nil , fmt . Errorf ( "missing auth config %q" , authInfoName )
2022-06-01 20:44:45 +02:00
}
var tlsConfig * promauth . TLSConfig
var basicAuth * promauth . BasicAuthConfig
var token , tokenFile string
if configAuthInfo != nil {
if err := configAuthInfo . validate ( ) ; err != nil {
2022-06-06 13:24:52 +02:00
return nil , fmt . Errorf ( "invalid auth config %q: %w" , authInfoName , err )
2022-06-01 20:44:45 +02:00
}
2022-06-06 13:24:52 +02:00
if strings . HasPrefix ( configClusterInfo . Server , "https://" ) {
tlsConfig = & promauth . TLSConfig {
CAFile : configClusterInfo . CertificateAuthority ,
ServerName : configClusterInfo . TLSServerName ,
InsecureSkipVerify : configClusterInfo . InsecureSkipTLSVerify ,
}
if len ( configClusterInfo . CertificateAuthorityData ) > 0 {
ca , err := base64 . StdEncoding . DecodeString ( configClusterInfo . CertificateAuthorityData )
if err != nil {
return nil , fmt . Errorf ( "cannot base64-decode certificate-authority-data from config %q at context %q: %w" , clusterInfoName , contextName , err )
}
tlsConfig . CA = ca
}
2022-06-01 20:44:45 +02:00
tlsConfig . CertFile = configAuthInfo . ClientCertificate
tlsConfig . KeyFile = configAuthInfo . ClientKey
if len ( configAuthInfo . ClientCertificateData ) > 0 {
2022-06-06 13:24:52 +02:00
cert , err := base64 . StdEncoding . DecodeString ( configAuthInfo . ClientCertificateData )
2022-06-01 20:44:45 +02:00
if err != nil {
2022-06-06 13:24:52 +02:00
return nil , fmt . Errorf ( "cannot base64-decode client-certificate-data from %q: %w" , authInfoName , err )
2022-06-01 20:44:45 +02:00
}
2022-06-06 13:24:52 +02:00
tlsConfig . Cert = cert
2022-06-01 20:44:45 +02:00
}
if len ( configAuthInfo . ClientKeyData ) > 0 {
2022-06-06 13:24:52 +02:00
key , err := base64 . StdEncoding . DecodeString ( configAuthInfo . ClientKeyData )
2022-06-01 20:44:45 +02:00
if err != nil {
2022-06-06 13:24:52 +02:00
return nil , fmt . Errorf ( "cannot base64-decode client-key-data from %q: %w" , authInfoName , err )
2022-06-01 20:44:45 +02:00
}
2022-06-06 13:24:52 +02:00
tlsConfig . Key = key
2022-06-01 20:44:45 +02:00
}
}
if len ( configAuthInfo . Username ) > 0 || len ( configAuthInfo . Password ) > 0 {
basicAuth = & promauth . BasicAuthConfig {
Username : configAuthInfo . Username ,
Password : promauth . NewSecret ( configAuthInfo . Password ) ,
}
}
token = configAuthInfo . Token
tokenFile = configAuthInfo . TokenFile
}
2022-06-06 13:24:52 +02:00
kc := & kubeConfig {
2022-06-01 20:44:45 +02:00
basicAuth : basicAuth ,
2022-06-06 13:24:52 +02:00
server : server ,
2022-06-01 20:44:45 +02:00
token : token ,
tokenFile : tokenFile ,
tlsConfig : tlsConfig ,
proxyURL : configClusterInfo . ProxyURL ,
}
2022-06-06 13:24:52 +02:00
return kc , nil
2022-06-01 20:44:45 +02:00
}