kops/federation/federation_configuration.go

387 lines
9.2 KiB
Go

/*
Copyright 2016 The Kubernetes Authors.
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.
*/
package federation
import (
"fmt"
"github.com/golang/glog"
apierrors "k8s.io/apimachinery/pkg/api/errors"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/pkg/api/v1"
"k8s.io/kops/federation/targets/kubernetestarget"
kopsapi "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/kubeconfig"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/fitasks"
)
const UserAdmin = "admin"
type FederationConfiguration struct {
Namespace string
ApiserverKeypair *fitasks.Keypair
ApiserverServiceName string
ApiserverSecretName string
KubeconfigSecretName string
}
func (o *FederationConfiguration) extractKubecfg(c *fi.Context, f *kopsapi.Federation) (*kubeconfig.KubeconfigBuilder, error) {
// TODO: move this
masterName := "api." + f.Spec.DNSName
k := kubeconfig.NewKubeconfigBuilder()
k.Server = "https://" + masterName
k.Context = "federation-" + f.ObjectMeta.Name
// CA Cert
caCert, _, err := c.Keystore.FindKeypair(fi.CertificateId_CA)
if err != nil {
return nil, err
}
if caCert == nil {
glog.Infof("No CA certificate in cluster %q", c)
return nil, nil
}
k.CACert, err = caCert.AsBytes()
if err != nil {
return nil, err
}
k8s := c.Target.(*kubernetestarget.KubernetesTarget).KubernetesClient
// Basic auth
secret, err := findSecret(k8s, o.Namespace, o.ApiserverSecretName)
if err != nil {
return nil, err
}
if secret == nil {
glog.Infof("No federation configuration in cluster %q", c)
return nil, nil
}
{
basicAuthData, err := o.findBasicAuth(secret)
if err != nil {
return nil, err
}
if basicAuthData == nil {
glog.Infof("No auth data in cluster %q", c)
return nil, nil
}
user := basicAuthData.FindUser(UserAdmin)
if user == nil {
glog.Infof("No auth data for user %q in cluster %q", UserAdmin, c)
return nil, nil
}
k.KubeUser = user.User
k.KubePassword = user.Secret
}
{
knownTokens, err := o.findKnownTokens(secret)
if err != nil {
return nil, err
}
if knownTokens == nil {
glog.Infof("No token data in cluster %q", c)
return nil, nil
}
user := knownTokens.FindUser(UserAdmin)
if user == nil {
glog.Infof("No token data for user %q in cluster %q", UserAdmin, c)
return nil, nil
}
k.KubeBearerToken = user.Secret
}
return k, nil
}
func (o *FederationConfiguration) findBasicAuth(secret *v1.Secret) (*AuthFile, error) {
var basicAuthData *AuthFile
var err error
if secret == nil {
return nil, nil
}
if secret.Data["basic-auth.csv"] != nil {
basicAuthData, err = ParseAuthFile(secret.Data["basic-auth.csv"])
if err != nil {
return nil, fmt.Errorf("error parsing auth file basic-auth.csv in secret %s/%s: %v", secret.Namespace, secret.Name, err)
}
}
return basicAuthData, nil
}
func (o *FederationConfiguration) findKnownTokens(secret *v1.Secret) (*AuthFile, error) {
var knownTokens *AuthFile
var err error
if secret == nil {
return nil, nil
}
if secret.Data["known-tokens.csv"] != nil {
knownTokens, err = ParseAuthFile(secret.Data["known-tokens.csv"])
if err != nil {
return nil, fmt.Errorf("error parsing auth file known-tokens.csv in secret %s/%s: %v", secret.Namespace, secret.Name, err)
}
}
return knownTokens, nil
}
func (o *FederationConfiguration) EnsureConfiguration(c *fi.Context) error {
caCert, _, err := c.Keystore.FindKeypair(fi.CertificateId_CA)
if err != nil {
return err
}
if caCert == nil {
return fmt.Errorf("cannot find CA certificate")
}
serverCert, serverKey, err := c.Keystore.FindKeypair(fi.StringValue(o.ApiserverKeypair.Name))
if err != nil {
return err
}
if serverCert == nil || serverKey == nil {
return fmt.Errorf("cannot find server keypair")
}
k8s := c.Target.(*kubernetestarget.KubernetesTarget).KubernetesClient
adminPassword := ""
//adminToken := ""
_, err = mutateSecret(k8s, o.Namespace, o.ApiserverSecretName, func(s *v1.Secret) (*v1.Secret, error) {
basicAuthData, err := o.findBasicAuth(s)
if err != nil {
return nil, err
}
knownTokens, err := o.findKnownTokens(s)
if err != nil {
return nil, err
}
{
if basicAuthData == nil {
basicAuthData = &AuthFile{}
}
u := basicAuthData.FindUser(UserAdmin)
if u == nil {
s, err := fi.CreateSecret()
if err != nil {
return nil, err
}
err = basicAuthData.Add(&AuthFileLine{User: UserAdmin, Secret: string(s.Data), Role: "admin"})
if err != nil {
return nil, err
}
adminPassword = string(s.Data)
} else {
adminPassword = u.Secret
}
}
{
if knownTokens == nil {
knownTokens = &AuthFile{}
}
u := knownTokens.FindUser(UserAdmin)
if u == nil {
s, err := fi.CreateSecret()
if err != nil {
return nil, err
}
err = knownTokens.Add(&AuthFileLine{User: UserAdmin, Secret: string(s.Data), Role: "admin"})
if err != nil {
return nil, err
}
//adminToken = string(s.Data)
} else {
//adminToken = u.Secret
}
}
if s == nil {
s = &v1.Secret{}
s.Type = v1.SecretTypeOpaque
}
if s.Data == nil {
s.Data = make(map[string][]byte)
}
{
b, err := caCert.AsBytes()
if err != nil {
return nil, err
}
s.Data["ca.crt"] = b
}
{
b, err := serverCert.AsBytes()
if err != nil {
return nil, err
}
s.Data["server.cert"] = b
}
{
b, err := serverKey.AsBytes()
if err != nil {
return nil, err
}
s.Data["server.key"] = b
}
s.Data["basic-auth.csv"] = []byte(basicAuthData.Encode())
s.Data["known-tokens.csv"] = []byte(knownTokens.Encode())
return s, nil
})
// TODO: Prefer username / password or token?
user := kubeconfig.KubectlUser{
Username: UserAdmin,
Password: adminPassword,
//Token: adminToken,
}
err = o.ensureSecretKubeconfig(c, caCert, user)
if err != nil {
return err
}
return nil
}
func (o *FederationConfiguration) ensureSecretKubeconfig(c *fi.Context, caCert *fi.Certificate, user kubeconfig.KubectlUser) error {
k8s := c.Target.(*kubernetestarget.KubernetesTarget).KubernetesClient
_, err := mutateSecret(k8s, o.Namespace, o.KubeconfigSecretName, func(s *v1.Secret) (*v1.Secret, error) {
var kubeconfigData []byte
var err error
{
conf := &kubeconfig.KubectlConfig{
ApiVersion: "v1",
Kind: "Config",
}
cluster := &kubeconfig.KubectlClusterWithName{
Name: o.ApiserverServiceName,
Cluster: kubeconfig.KubectlCluster{
Server: "https://" + o.ApiserverServiceName,
},
}
if caCert != nil {
caCertData, err := caCert.AsBytes()
if err != nil {
return nil, err
}
cluster.Cluster.CertificateAuthorityData = caCertData
}
conf.Clusters = append(conf.Clusters, cluster)
user := &kubeconfig.KubectlUserWithName{
Name: o.ApiserverServiceName,
User: user,
}
conf.Users = append(conf.Users, user)
context := &kubeconfig.KubectlContextWithName{
Name: o.ApiserverServiceName,
Context: kubeconfig.KubectlContext{
Cluster: cluster.Name,
User: user.Name,
},
}
conf.CurrentContext = o.ApiserverServiceName
conf.Contexts = append(conf.Contexts, context)
kubeconfigData, err = kopsapi.ToRawYaml(conf)
if err != nil {
return nil, fmt.Errorf("error building kubeconfig: %v", err)
}
}
if s == nil {
s = &v1.Secret{}
s.Type = v1.SecretTypeOpaque
}
if s.Data == nil {
s.Data = make(map[string][]byte)
}
s.Data["kubeconfig"] = kubeconfigData
return s, nil
})
return err
}
func findSecret(k8s kubernetes.Interface, namespace, name string) (*v1.Secret, error) {
glog.V(2).Infof("querying k8s for secret %s/%s", namespace, name)
s, err := k8s.CoreV1().Secrets(namespace).Get(name, meta_v1.GetOptions{})
if err != nil {
if apierrors.IsNotFound(err) {
return nil, nil
} else {
return nil, fmt.Errorf("error reading secret %s/%s: %v", namespace, name, err)
}
}
return s, nil
}
func mutateSecret(k8s kubernetes.Interface, namespace string, name string, fn func(s *v1.Secret) (*v1.Secret, error)) (*v1.Secret, error) {
existing, err := findSecret(k8s, namespace, name)
if err != nil {
return nil, err
}
createObject := existing == nil
updated, err := fn(existing)
if err != nil {
return nil, err
}
updated.Namespace = namespace
updated.Name = name
if createObject {
glog.V(2).Infof("creating k8s secret %s/%s", namespace, name)
created, err := k8s.CoreV1().Secrets(namespace).Create(updated)
if err != nil {
return nil, fmt.Errorf("error creating secret %s/%s: %v", namespace, name, err)
}
return created, nil
} else {
// TODO: Check dirty?
glog.V(2).Infof("updating k8s secret %s/%s", namespace, name)
updated, err := k8s.Core().Secrets(namespace).Update(updated)
if err != nil {
return nil, fmt.Errorf("error updating secret %s/%s: %v", namespace, name, err)
}
return updated, nil
}
}