// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package sprawl
import (
"bytes"
"context"
"fmt"
"io"
"github.com/hashicorp/consul/testing/deployer/sprawl/internal/secrets"
"github.com/hashicorp/consul/testing/deployer/topology"
)
const (
consulUID = "100"
consulGID = "1000"
consulUserArg = consulUID + ":" + consulGID
)
func tlsPrefixFromNode ( node * topology . Node ) string {
switch node . Kind {
case topology . NodeKindServer :
return node . Partition + "." + node . Name + ".server"
case topology . NodeKindClient :
return node . Partition + "." + node . Name + ".client"
default :
return ""
}
}
func tlsCertCreateCommand ( node * topology . Node ) string {
if node . IsServer ( ) {
return fmt . Sprintf ( ` consul tls cert create -server -dc=%s -node=%s ` , node . Datacenter , node . PodName ( ) )
} else {
return fmt . Sprintf ( ` consul tls cert create -client -dc=%s ` , node . Datacenter )
}
}
func ( s * Sprawl ) initTLS ( ctx context . Context ) error {
for _ , cluster := range s . topology . Clusters {
var buf bytes . Buffer
// Create the CA if not already done, and proceed to do all of the
// consul CLI calls inside of a throwaway temp directory.
buf . WriteString ( `
if [ [ ! - f consul - agent - ca - key . pem || ! - f consul - agent - ca . pem ] ] ; then
consul tls ca create
fi
rm - rf tmp
mkdir - p tmp
cp - a consul - agent - ca - key . pem consul - agent - ca . pem tmp
cd tmp
` )
for _ , node := range cluster . Nodes {
if ! node . IsAgent ( ) || node . Disabled {
continue
}
node . TLSCertPrefix = tlsPrefixFromNode ( node )
if node . TLSCertPrefix == "" {
continue
}
expectPrefix := cluster . Datacenter + "-" + string ( node . Kind ) + "-consul-0"
// Conditionally generate these in isolation and rename them to
// not rely upon the numerical indexing.
buf . WriteString ( fmt . Sprintf ( `
if [ [ ! - f % [ 1 ] s || ! - f % [ 2 ] s ] ] ; then
rm - f % [ 3 ] s % [ 4 ] s
% [ 5 ] s
mv - f % [ 3 ] s % [ 1 ] s
mv - f % [ 4 ] s % [ 2 ] s
fi
` ,
"../" + node . TLSCertPrefix + "-key.pem" , "../" + node . TLSCertPrefix + ".pem" ,
expectPrefix + "-key.pem" , expectPrefix + ".pem" ,
tlsCertCreateCommand ( node ) ,
) )
}
err := s . runner . DockerExec ( ctx , [ ] string {
"run" ,
"--rm" ,
"-i" ,
"--net=none" ,
"-v" , cluster . TLSVolumeName + ":/data" ,
// TODO: latest busted?
// https://hashicorp.slack.com/archives/C03EUN3QF1C/p1691784078972959
"busybox:1.34" ,
"sh" , "-c" ,
// Need this so the permissions stick; docker seems to treat unused volumes differently.
` touch /data/VOLUME_PLACEHOLDER && chown -R ` + consulUserArg + ` /data ` ,
} , io . Discard , nil )
if err != nil {
return fmt . Errorf ( "could not initialize docker volume for cert data %q: %w" , cluster . TLSVolumeName , err )
}
err = s . runner . DockerExec ( ctx , [ ] string { "run" ,
"--rm" ,
"-i" ,
"--net=none" ,
"-u" , consulUserArg ,
"-v" , cluster . TLSVolumeName + ":/data" ,
"-w" , "/data" ,
"--entrypoint" , "" ,
cluster . Images . Consul ,
"/bin/sh" , "-ec" , buf . String ( ) ,
} , io . Discard , nil )
if err != nil {
return fmt . Errorf ( "could not create all necessary TLS certificates in docker volume: %v" , err )
}
var capture bytes . Buffer
err = s . runner . DockerExec ( ctx , [ ] string { "run" ,
"--rm" ,
"-i" ,
"--net=none" ,
"-u" , consulUserArg ,
"-v" , cluster . TLSVolumeName + ":/data" ,
"-w" , "/data" ,
"busybox:1.34" ,
"cat" ,
"/data/consul-agent-ca.pem" ,
} , & capture , nil )
if err != nil {
return fmt . Errorf ( "could not read CA PEM from docker volume: %v" , err )
}
caPEM := capture . String ( )
if caPEM == "" {
return fmt . Errorf ( "found empty CA PEM" )
}
s . secrets . SaveGeneric ( cluster . Name , secrets . CAPEM , caPEM )
}
return nil
}