2021-12-02 21:19:16 +00:00
package cert
import (
2022-12-10 00:20:51 +00:00
"bytes"
"fmt"
2021-12-02 21:19:16 +00:00
"os"
"path/filepath"
"strconv"
"time"
"github.com/erikdubbelboer/gspt"
2023-08-01 15:55:34 +00:00
"github.com/k3s-io/k3s/pkg/agent/util"
2022-12-10 00:20:51 +00:00
"github.com/k3s-io/k3s/pkg/bootstrap"
2022-03-02 23:47:27 +00:00
"github.com/k3s-io/k3s/pkg/cli/cmds"
2022-12-10 00:20:51 +00:00
"github.com/k3s-io/k3s/pkg/clientaccess"
2022-03-02 23:47:27 +00:00
"github.com/k3s-io/k3s/pkg/daemons/config"
"github.com/k3s-io/k3s/pkg/daemons/control/deps"
"github.com/k3s-io/k3s/pkg/datadir"
"github.com/k3s-io/k3s/pkg/server"
"github.com/k3s-io/k3s/pkg/version"
2021-12-02 21:19:16 +00:00
"github.com/otiai10/copy"
2022-12-10 00:20:51 +00:00
"github.com/pkg/errors"
2021-12-02 21:19:16 +00:00
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
const (
adminService = "admin"
apiServerService = "api-server"
controllerManagerService = "controller-manager"
schedulerService = "scheduler"
etcdService = "etcd"
programControllerService = "-controller"
authProxyService = "auth-proxy"
cloudControllerService = "cloud-controller"
kubeletService = "kubelet"
kubeProxyService = "kube-proxy"
k3sServerService = "-server"
)
2021-12-09 16:57:13 +00:00
var services = [ ] string {
adminService ,
apiServerService ,
controllerManagerService ,
schedulerService ,
etcdService ,
version . Program + programControllerService ,
authProxyService ,
cloudControllerService ,
kubeletService ,
kubeProxyService ,
version . Program + k3sServerService ,
}
2022-12-10 00:20:51 +00:00
func commandSetup ( app * cli . Context , cfg * cmds . Server , sc * server . Config ) ( string , error ) {
2021-12-02 21:19:16 +00:00
gspt . SetProcTitle ( os . Args [ 0 ] )
dataDir , err := datadir . Resolve ( cfg . DataDir )
if err != nil {
2022-12-10 00:20:51 +00:00
return "" , err
2021-12-02 21:19:16 +00:00
}
2022-12-10 00:20:51 +00:00
sc . ControlConfig . DataDir = filepath . Join ( dataDir , "server" )
if cfg . Token == "" {
fp := filepath . Join ( sc . ControlConfig . DataDir , "token" )
tokenByte , err := os . ReadFile ( fp )
if err != nil {
return "" , err
}
cfg . Token = string ( bytes . TrimRight ( tokenByte , "\n" ) )
}
sc . ControlConfig . Token = cfg . Token
2023-02-08 00:37:10 +00:00
sc . ControlConfig . Runtime = config . NewRuntime ( nil )
2022-12-10 00:20:51 +00:00
return dataDir , nil
2021-12-02 21:19:16 +00:00
}
2022-12-10 00:20:51 +00:00
func Rotate ( app * cli . Context ) error {
2021-12-02 21:19:16 +00:00
if err := cmds . InitLogging ( ) ; err != nil {
return err
}
return rotate ( app , & cmds . ServerConfig )
}
func rotate ( app * cli . Context , cfg * cmds . Server ) error {
var serverConfig server . Config
2022-12-10 00:20:51 +00:00
dataDir , err := commandSetup ( app , cfg , & serverConfig )
2021-12-02 21:19:16 +00:00
if err != nil {
return err
}
2022-02-24 19:01:14 +00:00
deps . CreateRuntimeCertFiles ( & serverConfig . ControlConfig )
2021-12-02 21:19:16 +00:00
2021-12-09 16:57:13 +00:00
if err := validateCertConfig ( ) ; err != nil {
return err
}
2022-12-10 00:20:51 +00:00
agentDataDir := filepath . Join ( dataDir , "agent" )
tlsBackupDir , err := backupCertificates ( serverConfig . ControlConfig . DataDir , agentDataDir )
2021-12-02 21:19:16 +00:00
if err != nil {
return err
}
if len ( cmds . ServicesList ) == 0 {
2022-12-10 00:20:51 +00:00
// detecting if the command is being run on an agent or server
_ , err := os . Stat ( serverConfig . ControlConfig . DataDir )
2021-12-02 21:19:16 +00:00
if err != nil {
if ! os . IsNotExist ( err ) {
return err
}
logrus . Infof ( "Agent detected, rotating agent certificates" )
cmds . ServicesList = [ ] string {
kubeletService ,
kubeProxyService ,
version . Program + programControllerService ,
}
} else {
logrus . Infof ( "Server detected, rotating server certificates" )
cmds . ServicesList = [ ] string {
adminService ,
etcdService ,
apiServerService ,
controllerManagerService ,
cloudControllerService ,
schedulerService ,
version . Program + k3sServerService ,
version . Program + programControllerService ,
authProxyService ,
kubeletService ,
kubeProxyService ,
}
}
}
fileList := [ ] string { }
for _ , service := range cmds . ServicesList {
logrus . Infof ( "Rotating certificates for %s service" , service )
switch service {
case adminService :
fileList = append ( fileList ,
serverConfig . ControlConfig . Runtime . ClientAdminCert ,
serverConfig . ControlConfig . Runtime . ClientAdminKey )
case apiServerService :
fileList = append ( fileList ,
serverConfig . ControlConfig . Runtime . ClientKubeAPICert ,
serverConfig . ControlConfig . Runtime . ClientKubeAPIKey ,
serverConfig . ControlConfig . Runtime . ServingKubeAPICert ,
serverConfig . ControlConfig . Runtime . ServingKubeAPIKey )
case controllerManagerService :
fileList = append ( fileList ,
serverConfig . ControlConfig . Runtime . ClientControllerCert ,
serverConfig . ControlConfig . Runtime . ClientControllerKey )
case schedulerService :
fileList = append ( fileList ,
serverConfig . ControlConfig . Runtime . ClientSchedulerCert ,
serverConfig . ControlConfig . Runtime . ClientSchedulerKey )
case etcdService :
fileList = append ( fileList ,
serverConfig . ControlConfig . Runtime . ClientETCDCert ,
serverConfig . ControlConfig . Runtime . ClientETCDKey ,
serverConfig . ControlConfig . Runtime . ServerETCDCert ,
serverConfig . ControlConfig . Runtime . ServerETCDKey ,
serverConfig . ControlConfig . Runtime . PeerServerClientETCDCert ,
serverConfig . ControlConfig . Runtime . PeerServerClientETCDKey )
case cloudControllerService :
fileList = append ( fileList ,
serverConfig . ControlConfig . Runtime . ClientCloudControllerCert ,
serverConfig . ControlConfig . Runtime . ClientCloudControllerKey )
case version . Program + k3sServerService :
2022-12-10 00:20:51 +00:00
dynamicListenerRegenFilePath := filepath . Join ( serverConfig . ControlConfig . DataDir , "tls" , "dynamic-cert-regenerate" )
2022-10-08 00:36:57 +00:00
if err := os . WriteFile ( dynamicListenerRegenFilePath , [ ] byte { } , 0600 ) ; err != nil {
2021-12-02 21:19:16 +00:00
return err
}
logrus . Infof ( "Rotating dynamic listener certificate" )
case version . Program + programControllerService :
fileList = append ( fileList ,
serverConfig . ControlConfig . Runtime . ClientK3sControllerCert ,
serverConfig . ControlConfig . Runtime . ClientK3sControllerKey ,
filepath . Join ( agentDataDir , "client-" + version . Program + "-controller.crt" ) ,
filepath . Join ( agentDataDir , "client-" + version . Program + "-controller.key" ) )
case authProxyService :
fileList = append ( fileList ,
serverConfig . ControlConfig . Runtime . ClientAuthProxyCert ,
serverConfig . ControlConfig . Runtime . ClientAuthProxyKey )
case kubeletService :
fileList = append ( fileList ,
serverConfig . ControlConfig . Runtime . ClientKubeletKey ,
serverConfig . ControlConfig . Runtime . ServingKubeletKey ,
filepath . Join ( agentDataDir , "client-kubelet.crt" ) ,
filepath . Join ( agentDataDir , "client-kubelet.key" ) ,
filepath . Join ( agentDataDir , "serving-kubelet.crt" ) ,
filepath . Join ( agentDataDir , "serving-kubelet.key" ) )
case kubeProxyService :
fileList = append ( fileList ,
serverConfig . ControlConfig . Runtime . ClientKubeProxyCert ,
serverConfig . ControlConfig . Runtime . ClientKubeProxyKey ,
filepath . Join ( agentDataDir , "client-kube-proxy.crt" ) ,
filepath . Join ( agentDataDir , "client-kube-proxy.key" ) )
default :
logrus . Fatalf ( "%s is not a recognized service" , service )
}
}
for _ , file := range fileList {
if err := os . Remove ( file ) ; err == nil {
logrus . Debugf ( "file %s is deleted" , file )
}
}
logrus . Infof ( "Successfully backed up certificates for all services to path %s, please restart %s server or agent to rotate certificates" , tlsBackupDir , version . Program )
return nil
}
func backupCertificates ( serverDataDir , agentDataDir string ) ( string , error ) {
serverTLSDir := filepath . Join ( serverDataDir , "tls" )
tlsBackupDir := filepath . Join ( serverDataDir , "tls-" + strconv . Itoa ( int ( time . Now ( ) . Unix ( ) ) ) )
if _ , err := os . Stat ( serverTLSDir ) ; err != nil {
return "" , err
}
if err := copy . Copy ( serverTLSDir , tlsBackupDir ) ; err != nil {
return "" , err
}
2023-08-01 15:55:34 +00:00
certs := [ ] string {
"client-" + version . Program + "-controller.crt" ,
"client-" + version . Program + "-controller.key" ,
"client-kubelet.crt" ,
"client-kubelet.key" ,
"serving-kubelet.crt" ,
"serving-kubelet.key" ,
"client-kube-proxy.crt" ,
"client-kube-proxy.key" ,
2021-12-02 21:19:16 +00:00
}
2023-08-01 15:55:34 +00:00
for _ , cert := range certs {
agentCert := filepath . Join ( agentDataDir , cert )
tlsBackupCert := filepath . Join ( tlsBackupDir , cert )
if err := util . CopyFile ( agentCert , tlsBackupCert , true ) ; err != nil {
2021-12-02 21:19:16 +00:00
return "" , err
}
}
return tlsBackupDir , nil
}
2021-12-09 16:57:13 +00:00
func validService ( svc string ) bool {
for _ , service := range services {
if svc == service {
return true
}
}
return false
}
func validateCertConfig ( ) error {
for _ , s := range cmds . ServicesList {
if ! validService ( s ) {
return errors . New ( "Service " + s + " is not recognized" )
}
}
return nil
}
2022-12-10 00:20:51 +00:00
func RotateCA ( app * cli . Context ) error {
if err := cmds . InitLogging ( ) ; err != nil {
return err
}
return rotateCA ( app , & cmds . ServerConfig , & cmds . CertRotateCAConfig )
}
func rotateCA ( app * cli . Context , cfg * cmds . Server , sync * cmds . CertRotateCA ) error {
var serverConfig server . Config
_ , err := commandSetup ( app , cfg , & serverConfig )
if err != nil {
return err
}
2022-12-08 23:59:21 +00:00
info , err := clientaccess . ParseAndValidateToken ( cmds . ServerConfig . ServerURL , serverConfig . ControlConfig . Token , clientaccess . WithUser ( "server" ) )
2022-12-10 00:20:51 +00:00
if err != nil {
return err
}
// Set up dummy server config for reading new bootstrap data from disk.
tmpServer := & config . Control {
2023-02-08 00:37:10 +00:00
Runtime : config . NewRuntime ( nil ) ,
2023-02-13 22:43:22 +00:00
DataDir : sync . CACertPath ,
2022-12-10 00:20:51 +00:00
}
deps . CreateRuntimeCertFiles ( tmpServer )
// Override these paths so that we don't get warnings when they don't exist, as the user is not expected to provide them.
tmpServer . Runtime . PasswdFile = "/dev/null"
tmpServer . Runtime . IPSECKey = "/dev/null"
buf := & bytes . Buffer { }
if err := bootstrap . ReadFromDisk ( buf , & tmpServer . Runtime . ControlRuntimeBootstrap ) ; err != nil {
return err
}
url := fmt . Sprintf ( "/v1-%s/cert/cacerts?force=%t" , version . Program , sync . Force )
if err = info . Put ( url , buf . Bytes ( ) ) ; err != nil {
return errors . Wrap ( err , "see server log for details" )
}
fmt . Println ( "certificates saved to datastore" )
return nil
}