2017-10-31 13:55:35 +00:00
package pki
import (
"context"
2019-03-18 18:27:59 +00:00
"crypto/rsa"
2017-10-31 13:55:35 +00:00
"fmt"
2017-11-01 21:46:43 +00:00
"io/ioutil"
2017-11-29 20:33:08 +00:00
"os"
2018-01-10 23:03:08 +00:00
"path"
2019-03-18 18:27:59 +00:00
"strings"
2017-10-31 13:55:35 +00:00
"time"
"github.com/docker/docker/api/types/container"
2020-08-14 16:42:37 +00:00
"github.com/docker/docker/pkg/archive"
2017-10-31 13:55:35 +00:00
"github.com/rancher/rke/docker"
"github.com/rancher/rke/hosts"
2018-01-09 22:10:56 +00:00
"github.com/rancher/rke/log"
2019-08-19 17:53:15 +00:00
"github.com/rancher/rke/pki/cert"
2020-07-11 16:24:19 +00:00
v3 "github.com/rancher/rke/types"
2017-11-13 21:28:38 +00:00
"github.com/sirupsen/logrus"
2017-10-31 13:55:35 +00:00
)
2018-09-13 00:29:53 +00:00
const (
StateDeployerContainerName = "cluster-state-deployer"
)
2020-08-05 22:39:25 +00:00
func DeployCertificatesOnPlaneHost (
ctx context . Context ,
host * hosts . Host ,
rkeConfig v3 . RancherKubernetesEngineConfig ,
crtMap map [ string ] CertificatePKI ,
certDownloaderImage string ,
prsMap map [ string ] v3 . PrivateRegistry ,
forceDeploy bool ,
env [ ] string ) error {
2018-02-06 19:25:54 +00:00
crtBundle := GenerateRKENodeCerts ( ctx , rkeConfig , host . Address , crtMap )
2019-07-24 20:25:14 +00:00
// Strip CA key as its sensitive and unneeded on nodes without controlplane role
if ! host . IsControl {
caCert := crtBundle [ CACertName ]
caCert . Key = nil
caCert . KeyEnvName = ""
caCert . KeyPath = ""
crtBundle [ CACertName ] = caCert
}
2018-02-06 19:25:54 +00:00
for _ , crt := range crtBundle {
env = append ( env , crt . ToEnv ( ) ... )
2018-01-16 23:10:14 +00:00
}
2019-01-14 17:51:20 +00:00
if forceDeploy {
2018-08-20 04:37:04 +00:00
env = append ( env , "FORCE_DEPLOY=true" )
}
2019-07-24 19:58:50 +00:00
if host . IsEtcd &&
rkeConfig . Services . Etcd . UID != 0 &&
rkeConfig . Services . Etcd . GID != 0 {
env = append ( env ,
[ ] string { fmt . Sprintf ( "ETCD_UID=%d" , rkeConfig . Services . Etcd . UID ) ,
fmt . Sprintf ( "ETCD_GID=%d" , rkeConfig . Services . Etcd . GID ) } ... )
}
2018-02-01 16:25:19 +00:00
return doRunDeployer ( ctx , host , env , certDownloaderImage , prsMap )
2018-01-16 23:10:14 +00:00
}
2020-08-14 16:42:37 +00:00
func DeployStateOnPlaneHost ( ctx context . Context , host * hosts . Host , stateDownloaderImage string , prsMap map [ string ] v3 . PrivateRegistry , stateFilePath , snapshotName string ) error {
2018-09-13 00:29:53 +00:00
// remove existing container. Only way it's still here is if previous deployment failed
if err := docker . DoRemoveContainer ( ctx , host . DClient , StateDeployerContainerName , host . Address ) ; err != nil {
return err
}
2020-08-14 16:42:37 +00:00
// This is the location it needs to end up for rke-tools to pick it up and include it in the snapshot
// Example: /etc/kubernetes/snapshotname.rkestate
DestinationClusterStateFilePath := path . Join ( K8sBaseDir , "/" , fmt . Sprintf ( "%s%s" , snapshotName , ClusterStateExt ) )
// This is the location where the 1-on-1 copy from local will be placed in the container, this is later moved to DestinationClusterStateFilePath
// Example: /etc/kubernetes/cluster.rkestate
2020-08-21 12:10:05 +00:00
// Example: /etc/kubernetes/rancher-cluster.rkestate
baseStateFile := path . Base ( stateFilePath )
SourceClusterStateFilePath := path . Join ( K8sBaseDir , baseStateFile )
2020-08-14 16:42:37 +00:00
logrus . Infof ( "[state] Deploying state file to [%v] on host [%s]" , DestinationClusterStateFilePath , host . Address )
2018-09-13 00:29:53 +00:00
imageCfg := & container . Config {
Image : stateDownloaderImage ,
Cmd : [ ] string {
"sh" ,
"-c" ,
2020-08-14 16:42:37 +00:00
fmt . Sprintf ( "for i in $(seq 1 12); do if [ -f \"%[1]s\" ]; then echo \"File [%[1]s] present in this container\"; echo \"Moving [%[1]s] to [%[2]s]\"; mv %[1]s %[2]s; echo \"State file successfully moved to [%[2]s]\"; echo \"Changing permissions to 0400\"; chmod 400 %[2]s; break; else echo \"Waiting for file [%[1]s] to be successfully copied to this container, retry count $i\"; sleep 5; fi; done" , SourceClusterStateFilePath , DestinationClusterStateFilePath ) ,
2018-09-13 00:29:53 +00:00
} ,
}
hostCfg := & container . HostConfig {
Binds : [ ] string {
2021-07-29 06:59:54 +00:00
fmt . Sprintf ( "%s:/etc/kubernetes:z" , path . Join ( host . PrefixPath , "/etc/kubernetes" ) ) ,
2018-09-13 00:29:53 +00:00
} ,
Privileged : true ,
}
if err := docker . DoRunContainer ( ctx , host . DClient , imageCfg , hostCfg , StateDeployerContainerName , host . Address , "state" , prsMap ) ; err != nil {
return err
}
2020-08-14 16:42:37 +00:00
tarFile , err := archive . Tar ( stateFilePath , archive . Uncompressed )
if err != nil {
// Snapshot is still valid without containing the state file
logrus . Warnf ( "[state] Error during creating archive tar to copy for local cluster state file [%s] on host [%s]: %v" , stateFilePath , host . Address , err )
}
if err := docker . DoCopyToContainer ( ctx , host . DClient , "state" , StateDeployerContainerName , host . Address , K8sBaseDir , tarFile ) ; err != nil {
// Snapshot is still valid without containing the state file
logrus . Warnf ( "[state] Error during copying state file [%s] to node [%s]: %v" , stateFilePath , host . Address , err )
}
if _ , err := docker . WaitForContainer ( ctx , host . DClient , host . Address , StateDeployerContainerName ) ; err != nil {
return err
}
2021-06-06 07:16:44 +00:00
return docker . DoRemoveContainer ( ctx , host . DClient , StateDeployerContainerName , host . Address )
2018-09-13 00:29:53 +00:00
}
2018-01-31 17:50:55 +00:00
func doRunDeployer ( ctx context . Context , host * hosts . Host , containerEnv [ ] string , certDownloaderImage string , prsMap map [ string ] v3 . PrivateRegistry ) error {
2018-01-10 23:03:08 +00:00
// remove existing container. Only way it's still here is if previous deployment failed
isRunning := false
isRunning , err := docker . IsContainerRunning ( ctx , host . DClient , host . Address , CrtDownloaderContainer , true )
if err != nil {
return err
}
if isRunning {
if err := docker . RemoveContainer ( ctx , host . DClient , host . Address , CrtDownloaderContainer ) ; err != nil {
return err
}
}
2018-01-31 17:50:55 +00:00
if err := docker . UseLocalOrPull ( ctx , host . DClient , host . Address , certDownloaderImage , CertificatesServiceName , prsMap ) ; err != nil {
2017-10-31 13:55:35 +00:00
return err
}
2020-08-05 22:39:25 +00:00
2017-10-31 13:55:35 +00:00
imageCfg := & container . Config {
2017-12-05 01:29:29 +00:00
Image : certDownloaderImage ,
2018-04-19 15:53:55 +00:00
Cmd : [ ] string { "cert-deployer" } ,
2017-10-31 13:55:35 +00:00
Env : containerEnv ,
}
2020-08-05 22:39:25 +00:00
if host . IsWindows ( ) { // compatible with Windows
2019-07-05 04:23:48 +00:00
imageCfg = & container . Config {
Image : certDownloaderImage ,
Cmd : [ ] string {
"pwsh" , "-NoLogo" , "-NonInteractive" , "-File" , "c:/usr/bin/cert-deployer.ps1" ,
} ,
Env : containerEnv ,
}
}
2017-10-31 13:55:35 +00:00
hostCfg := & container . HostConfig {
Binds : [ ] string {
2021-07-29 06:59:54 +00:00
fmt . Sprintf ( "%s:/etc/kubernetes:z" , path . Join ( host . PrefixPath , "/etc/kubernetes" ) ) ,
2017-10-31 13:55:35 +00:00
} ,
2017-11-30 23:16:45 +00:00
Privileged : true ,
2017-10-31 13:55:35 +00:00
}
2020-08-05 22:39:25 +00:00
if host . IsWindows ( ) { // compatible with Windows
2019-07-05 04:23:48 +00:00
hostCfg = & container . HostConfig {
Binds : [ ] string {
fmt . Sprintf ( "%s:c:/etc/kubernetes" , path . Join ( host . PrefixPath , "/etc/kubernetes" ) ) ,
} ,
}
}
2019-06-11 09:39:45 +00:00
_ , err = docker . CreateContainer ( ctx , host . DClient , host . Address , CrtDownloaderContainer , imageCfg , hostCfg )
2017-10-31 13:55:35 +00:00
if err != nil {
2017-11-28 17:45:24 +00:00
return fmt . Errorf ( "Failed to create Certificates deployer container on host [%s]: %v" , host . Address , err )
2017-10-31 13:55:35 +00:00
}
2019-06-11 09:39:45 +00:00
if err := docker . StartContainer ( ctx , host . DClient , host . Address , CrtDownloaderContainer ) ; err != nil {
2017-11-28 17:45:24 +00:00
return fmt . Errorf ( "Failed to start Certificates deployer container on host [%s]: %v" , host . Address , err )
2017-10-31 13:55:35 +00:00
}
2019-06-11 09:39:45 +00:00
logrus . Debugf ( "[certificates] Successfully started Certificate deployer container: %s" , CrtDownloaderContainer )
2017-10-31 13:55:35 +00:00
for {
2018-01-09 22:10:56 +00:00
isDeployerRunning , err := docker . IsContainerRunning ( ctx , host . DClient , host . Address , CrtDownloaderContainer , false )
2017-10-31 13:55:35 +00:00
if err != nil {
return err
}
if isDeployerRunning {
time . Sleep ( 5 * time . Second )
continue
}
2019-06-11 09:39:45 +00:00
if err := docker . RemoveContainer ( ctx , host . DClient , host . Address , CrtDownloaderContainer ) ; err != nil {
2017-11-28 17:45:24 +00:00
return fmt . Errorf ( "Failed to delete Certificates deployer container on host [%s]: %v" , host . Address , err )
2017-10-31 13:55:35 +00:00
}
return nil
}
}
2017-11-01 21:46:43 +00:00
2018-01-09 22:10:56 +00:00
func DeployAdminConfig ( ctx context . Context , kubeConfig , localConfigPath string ) error {
2018-02-15 03:25:36 +00:00
if len ( kubeConfig ) == 0 {
2020-09-28 12:59:44 +00:00
logrus . Infof ( "kubeConfig is empty" )
2018-02-15 03:25:36 +00:00
return nil
}
2020-03-05 18:42:05 +00:00
logrus . Debugf ( "Deploying admin Kubeconfig locally at [%s]" , localConfigPath )
logrus . Tracef ( "Deploying admin Kubeconfig locally: %s" , kubeConfig )
2021-03-07 10:29:02 +00:00
err := ioutil . WriteFile ( localConfigPath , [ ] byte ( kubeConfig ) , 0600 )
2017-11-02 10:07:10 +00:00
if err != nil {
return fmt . Errorf ( "Failed to create local admin kubeconfig file: %v" , err )
2017-11-01 21:46:43 +00:00
}
2018-01-09 22:10:56 +00:00
log . Infof ( ctx , "Successfully Deployed local admin kubeconfig at [%s]" , localConfigPath )
2017-11-01 21:46:43 +00:00
return nil
}
2017-11-29 20:33:08 +00:00
2018-01-09 22:10:56 +00:00
func RemoveAdminConfig ( ctx context . Context , localConfigPath string ) {
log . Infof ( ctx , "Removing local admin Kubeconfig: %s" , localConfigPath )
2017-11-29 20:33:08 +00:00
if err := os . Remove ( localConfigPath ) ; err != nil {
2017-12-08 23:38:44 +00:00
logrus . Warningf ( "Failed to remove local admin Kubeconfig file: %v" , err )
return
2017-11-29 20:33:08 +00:00
}
2018-01-09 22:10:56 +00:00
log . Infof ( ctx , "Local admin Kubeconfig removed successfully" )
2017-11-29 20:33:08 +00:00
}
2018-01-10 23:03:08 +00:00
2018-02-06 19:25:54 +00:00
func DeployCertificatesOnHost ( ctx context . Context , host * hosts . Host , crtMap map [ string ] CertificatePKI , certDownloaderImage , certPath string , prsMap map [ string ] v3 . PrivateRegistry ) error {
2018-01-10 23:03:08 +00:00
env := [ ] string {
2018-01-16 23:10:14 +00:00
"CRTS_DEPLOY_PATH=" + certPath ,
2018-01-10 23:03:08 +00:00
}
2018-02-06 19:25:54 +00:00
for _ , crt := range crtMap {
env = append ( env , crt . ToEnv ( ) ... )
2018-01-10 23:03:08 +00:00
}
2018-01-31 17:50:55 +00:00
return doRunDeployer ( ctx , host , env , certDownloaderImage , prsMap )
2018-01-10 23:03:08 +00:00
}
2019-03-18 18:27:59 +00:00
func FetchCertificatesFromHost ( ctx context . Context , extraHosts [ ] * hosts . Host , host * hosts . Host , image , localConfigPath string , prsMap map [ string ] v3 . PrivateRegistry ) ( map [ string ] CertificatePKI , error ) {
// rebuilding the certificates. This should look better after refactoring pki
tmpCerts := make ( map [ string ] CertificatePKI )
crtList := map [ string ] bool {
CACertName : false ,
KubeAPICertName : false ,
KubeControllerCertName : true ,
KubeSchedulerCertName : true ,
KubeProxyCertName : true ,
KubeNodeCertName : true ,
KubeAdminCertName : false ,
RequestHeaderCACertName : false ,
APIProxyClientCertName : false ,
}
for _ , etcdHost := range extraHosts {
// Fetch etcd certificates
2019-07-24 20:25:14 +00:00
crtList [ GetCrtNameForHost ( etcdHost , EtcdCertName ) ] = false
2019-03-18 18:27:59 +00:00
}
for certName , config := range crtList {
certificate := CertificatePKI { }
crt , err := FetchFileFromHost ( ctx , GetCertTempPath ( certName ) , image , host , prsMap , CertFetcherContainer , "certificates" )
2019-08-29 18:59:47 +00:00
// Return error if the certificate file is not found but only if its not etcd or request header certificate
if err != nil && ! strings . HasPrefix ( certName , "kube-etcd" ) &&
certName != RequestHeaderCACertName &&
2019-09-06 19:41:42 +00:00
certName != APIProxyClientCertName &&
certName != KubeAdminCertName {
2019-03-18 18:27:59 +00:00
// IsErrNotFound doesn't catch this because it's a custom error
if isFileNotFoundErr ( err ) {
2019-08-29 18:59:47 +00:00
return nil , fmt . Errorf ( "Certificate %s is not found" , GetCertTempPath ( certName ) )
2019-03-18 18:27:59 +00:00
}
return nil , err
}
2019-08-29 18:59:47 +00:00
// If I can't find an etcd or request header ca I will not fail and will create it later.
if crt == "" && ( strings . HasPrefix ( certName , "kube-etcd" ) ||
certName == RequestHeaderCACertName ||
2019-09-06 19:41:42 +00:00
certName == APIProxyClientCertName ||
certName == KubeAdminCertName ) {
2019-03-18 18:27:59 +00:00
tmpCerts [ certName ] = CertificatePKI { }
continue
}
key , err := FetchFileFromHost ( ctx , GetKeyTempPath ( certName ) , image , host , prsMap , CertFetcherContainer , "certificate" )
2019-08-29 18:59:47 +00:00
if err != nil {
if isFileNotFoundErr ( err ) {
return nil , fmt . Errorf ( "Key %s is not found" , GetKeyTempPath ( certName ) )
}
return nil , err
}
2019-03-18 18:27:59 +00:00
if config {
config , err := FetchFileFromHost ( ctx , GetConfigTempPath ( certName ) , image , host , prsMap , CertFetcherContainer , "certificate" )
if err != nil {
2019-08-29 18:59:47 +00:00
if isFileNotFoundErr ( err ) {
return nil , fmt . Errorf ( "Config %s is not found" , GetConfigTempPath ( certName ) )
}
2019-03-18 18:27:59 +00:00
return nil , err
}
certificate . Config = config
}
parsedCert , err := cert . ParseCertsPEM ( [ ] byte ( crt ) )
if err != nil {
return nil , err
}
parsedKey , err := cert . ParsePrivateKeyPEM ( [ ] byte ( key ) )
if err != nil {
return nil , err
}
certificate . Certificate = parsedCert [ 0 ]
certificate . Key = parsedKey . ( * rsa . PrivateKey )
tmpCerts [ certName ] = certificate
logrus . Debugf ( "[certificates] Recovered certificate: %s" , certName )
}
if err := docker . RemoveContainer ( ctx , host . DClient , host . Address , CertFetcherContainer ) ; err != nil {
return nil , err
}
return populateCertMap ( tmpCerts , localConfigPath , extraHosts ) , nil
}
func FetchFileFromHost ( ctx context . Context , filePath , image string , host * hosts . Host , prsMap map [ string ] v3 . PrivateRegistry , containerName , state string ) ( string , error ) {
imageCfg := & container . Config {
Image : image ,
}
hostCfg := & container . HostConfig {
Binds : [ ] string {
2021-07-29 06:59:54 +00:00
fmt . Sprintf ( "%s:/etc/kubernetes:z" , path . Join ( host . PrefixPath , "/etc/kubernetes" ) ) ,
2019-03-18 18:27:59 +00:00
} ,
Privileged : true ,
}
isRunning , err := docker . IsContainerRunning ( ctx , host . DClient , host . Address , containerName , true )
if err != nil {
return "" , err
}
if ! isRunning {
if err := docker . DoRunContainer ( ctx , host . DClient , imageCfg , hostCfg , containerName , host . Address , state , prsMap ) ; err != nil {
return "" , err
}
}
file , err := docker . ReadFileFromContainer ( ctx , host . DClient , host . Address , containerName , filePath )
if err != nil {
return "" , err
}
return file , nil
}