diff --git a/cmd/homerunner/main.go b/cmd/homerunner/main.go index 9c650417..9b7651c9 100644 --- a/cmd/homerunner/main.go +++ b/cmd/homerunner/main.go @@ -11,7 +11,6 @@ import ( "github.com/matrix-org/complement/internal/config" "github.com/matrix-org/complement/internal/docker" - "github.com/matrix-org/complement/internal/federation" "github.com/sirupsen/logrus" ) @@ -25,6 +24,19 @@ type Config struct { Snapshot string } +func (c *Config) DeriveComplementConfig(baseImageURI string) *config.Complement { + cfg := &config.Complement{ + BaseImageURI: baseImageURI, + DebugLoggingEnabled: true, + SpawnHSTimeout: c.SpawnHSTimeout, + KeepBlueprints: c.KeepBlueprints, + BestEffort: true, + PackageNamespace: Pkg, + } + _ = cfg.GenerateCA() + return cfg +} + func NewConfig() *Config { cfg := &Config{ HomeserverLifetimeMins: 30, @@ -46,14 +58,7 @@ func NewConfig() *Config { } func cleanup(c *Config) { - cfg := &config.Complement{ - PackageNamespace: Pkg, - BaseImageURI: "nothing", - DebugLoggingEnabled: true, - SpawnHSTimeout: c.SpawnHSTimeout, - KeepBlueprints: c.KeepBlueprints, - BestEffort: true, - } + cfg := c.DeriveComplementConfig("nothing") builder, err := docker.NewBuilder(cfg) if err != nil { logrus.WithError(err).Fatalf("failed to run cleanup") @@ -69,11 +74,6 @@ func main() { } cleanup(cfg) - _, _, err = federation.GetOrCreateCaCert() - if err != nil { - logrus.Fatalf("failed to make CA certs") - } - if cfg.Snapshot != "" { logrus.Infof("Running in single-shot snapshot mode for request file '%s'", cfg.Snapshot) // pretend the file is the request diff --git a/cmd/homerunner/setup.go b/cmd/homerunner/setup.go index 4f3bf8db..a22a4873 100644 --- a/cmd/homerunner/setup.go +++ b/cmd/homerunner/setup.go @@ -7,7 +7,6 @@ import ( "time" "github.com/matrix-org/complement/internal/b" - "github.com/matrix-org/complement/internal/config" "github.com/matrix-org/complement/internal/docker" "github.com/sirupsen/logrus" ) @@ -36,13 +35,7 @@ func (r *Runtime) CreateDeployment(imageURI string, blueprint *b.Blueprint) (*do return nil, expires, fmt.Errorf("blueprint must be supplied") } namespace := "homerunner_" + blueprint.Name - cfg := &config.Complement{ - BaseImageURI: imageURI, - DebugLoggingEnabled: true, - SpawnHSTimeout: r.Config.SpawnHSTimeout, - BestEffort: true, - PackageNamespace: Pkg, - } + cfg := r.Config.DeriveComplementConfig(imageURI) builder, err := docker.NewBuilder(cfg) if err != nil { return nil, expires, err diff --git a/internal/config/config.go b/internal/config/config.go index aadcfcb9..1691e092 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,7 +1,14 @@ package config import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" "fmt" + "math/big" "os" "strconv" "strings" @@ -18,6 +25,10 @@ type Complement struct { KeepBlueprints []string // The namespace for all complement created blueprints and deployments PackageNamespace string + // Certificate Authority generated values for this run of complement. Homeservers will use this + // as a base to derive their own signed Federation certificates. + CACertificate *x509.Certificate + CAPrivateKey *rsa.PrivateKey } func NewConfigFromEnvVars() *Complement { @@ -37,9 +48,40 @@ func NewConfigFromEnvVars() *Complement { panic("COMPLEMENT_BASE_IMAGE must be set") } cfg.PackageNamespace = "pkg" + + // create CA certs and keys + if err := cfg.GenerateCA(); err != nil { + panic("Failed to generate CA certificate/key: " + err.Error()) + } + return cfg } +func (c *Complement) GenerateCA() error { + cert, key, err := generateCAValues() + if err != nil { + return err + } + c.CACertificate = cert + c.CAPrivateKey = key + return nil +} + +func (c *Complement) CACertificateBytes() ([]byte, error) { + cert := bytes.NewBuffer(nil) + err := pem.Encode(cert, &pem.Block{Type: "CERTIFICATE", Bytes: c.CACertificate.Raw}) + return cert.Bytes(), err +} + +func (c *Complement) CAPrivateKeyBytes() ([]byte, error) { + caKey := bytes.NewBuffer(nil) + err := pem.Encode(caKey, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(c.CAPrivateKey), + }) + return caKey.Bytes(), err +} + func parseEnvWithDefault(key string, def int) int { s := os.Getenv(key) if s != "" { @@ -52,3 +94,48 @@ func parseEnvWithDefault(key string, def int) int { } return def } + +// Generate a certificate and private key +func generateCAValues() (*x509.Certificate, *rsa.PrivateKey, error) { + // valid for 10 years + certificateDuration := time.Hour * 24 * 365 * 10 + priv, err := rsa.GenerateKey(rand.Reader, 4096) + if err != nil { + return nil, nil, err + } + notBefore := time.Now() + notAfter := notBefore.Add(certificateDuration) + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, nil, err + } + caCert := x509.Certificate{ + SerialNumber: serialNumber, + NotBefore: notBefore, + NotAfter: notAfter, + IsCA: true, + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature | x509.KeyUsageCRLSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + Subject: pkix.Name{ + Organization: []string{"matrix.org"}, + Country: []string{"GB"}, + Province: []string{"London"}, + Locality: []string{"London"}, + StreetAddress: []string{"123 Street"}, + PostalCode: []string{"12345"}, + }, + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &caCert, &caCert, &priv.PublicKey, priv) + if err != nil { + return nil, nil, err + } + selfSignedCert, err := x509.ParseCertificates(derBytes) + if err != nil { + return nil, nil, err + } + + return selfSignedCert[0], priv, nil +} diff --git a/internal/docker/builder.go b/internal/docker/builder.go index 96557821..ff29e634 100644 --- a/internal/docker/builder.go +++ b/internal/docker/builder.go @@ -41,10 +41,8 @@ var ( const complementLabel = "complement_context" type Builder struct { - Config *config.Complement - CSAPIPort int - FederationPort int - Docker *client.Client + Config *config.Complement + Docker *client.Client } func NewBuilder(cfg *config.Complement) (*Builder, error) { @@ -53,10 +51,8 @@ func NewBuilder(cfg *config.Complement) (*Builder, error) { return nil, err } return &Builder{ - Docker: cli, - Config: cfg, - CSAPIPort: 8008, - FederationPort: 8448, + Docker: cli, + Config: cfg, }, nil } @@ -362,9 +358,9 @@ func (d *Builder) deployBaseImage(blueprintName string, hs b.Homeserver, context asIDToRegistrationMap := asIDToRegistrationFromLabels(labelsForApplicationServices(hs)) return deployImage( - d.Docker, d.Config.BaseImageURI, d.CSAPIPort, fmt.Sprintf("complement_%s", contextStr), + d.Docker, d.Config.BaseImageURI, fmt.Sprintf("complement_%s", contextStr), d.Config.PackageNamespace, blueprintName, hs.Name, asIDToRegistrationMap, contextStr, - networkID, d.Config.SpawnHSTimeout, d.Config.DebugLoggingEnabled, + networkID, d.Config, ) } diff --git a/internal/docker/deployer.go b/internal/docker/deployer.go index 68b62341..eb463665 100644 --- a/internal/docker/deployer.go +++ b/internal/docker/deployer.go @@ -22,7 +22,6 @@ import ( "log" "net/http" "net/url" - "os" "runtime" "sync" "time" @@ -71,6 +70,7 @@ func (d *Deployer) Deploy(ctx context.Context, blueprintName string) (*Deploymen Deployer: d, BlueprintName: blueprintName, HS: make(map[string]HomeserverDeployment), + Config: d.config, } images, err := d.Docker.ImageList(ctx, types.ImageListOptions{ Filters: label( @@ -106,8 +106,8 @@ func (d *Deployer) Deploy(ctx context.Context, blueprintName string) (*Deploymen // TODO: Make CSAPI port configurable deployment, err := deployImage( - d.Docker, img.ID, 8008, fmt.Sprintf("complement_%s_%s_%s_%d", d.config.PackageNamespace, d.DeployNamespace, contextStr, counter), - d.config.PackageNamespace, blueprintName, hsName, asIDToRegistrationMap, contextStr, networkID, d.config.SpawnHSTimeout, d.config.DebugLoggingEnabled, + d.Docker, img.ID, fmt.Sprintf("complement_%s_%s_%s_%d", d.config.PackageNamespace, d.DeployNamespace, contextStr, counter), + d.config.PackageNamespace, blueprintName, hsName, asIDToRegistrationMap, contextStr, networkID, d.config, ) if err != nil { if deployment != nil && deployment.ContainerID != "" { @@ -157,10 +157,10 @@ func (d *Deployer) Destroy(dep *Deployment, printServerLogs bool) { } } +// nolint func deployImage( - docker *client.Client, imageID string, csPort int, containerName, pkgNamespace, blueprintName, hsName string, - asIDToRegistrationMap map[string]string, contextStr, networkID string, spawnHSTimeout time.Duration, - debugLoggingEnabled bool, + docker *client.Client, imageID string, containerName, pkgNamespace, blueprintName, hsName string, + asIDToRegistrationMap map[string]string, contextStr, networkID string, cfg *config.Complement, ) (*HomeserverDeployment, error) { ctx := context.Background() var extraHosts []string @@ -177,9 +177,6 @@ func deployImage( toMount := []Volume{ &VolumeAppService{}, } - if os.Getenv("COMPLEMENT_CA") == "true" { - toMount = append(toMount, &VolumeCA{}) - } for _, m := range toMount { err = m.Prepare(ctx, docker, contextStr) @@ -191,8 +188,8 @@ func deployImage( env := []string{ "SERVER_NAME=" + hsName, - // TODO: Remove once HS images don't rely on this anymore - "COMPLEMENT_CA=" + os.Getenv("COMPLEMENT_CA"), + // TODO: Remove once Synapse images don't rely on this anymore + "COMPLEMENT_CA=1", } body, err := docker.ContainerCreate(ctx, &container.Config{ @@ -222,41 +219,41 @@ func deployImage( } containerID := body.ID - if debugLoggingEnabled { + if cfg.DebugLoggingEnabled { log.Printf("%s: Created container %s", contextStr, containerID) } // Create the application service files for asID, registration := range asIDToRegistrationMap { - // Create a fake/virtual file in memory that we can copy to the container - // via https://stackoverflow.com/a/52131297/796832 - var buf bytes.Buffer - tw := tar.NewWriter(&buf) - err = tw.WriteHeader(&tar.Header{ - Name: fmt.Sprintf("/appservices/%s.yaml", url.PathEscape(asID)), - Mode: 0777, - Size: int64(len(registration)), - }) - if err != nil { - return nil, fmt.Errorf("failed to copy regstration to container: %v", err) - } - tw.Write([]byte(registration)) - tw.Close() - - // Put our new fake file in the container volume - err = docker.CopyToContainer(context.Background(), containerID, "/", &buf, types.CopyToContainerOptions{ - AllowOverwriteDirWithFile: false, - }) + err = copyToContainer(docker, containerID, fmt.Sprintf("/appservices/%s.yaml", url.PathEscape(asID)), []byte(registration)) if err != nil { return nil, err } } + // Copy CA certificate and key + certBytes, err := cfg.CACertificateBytes() + if err != nil { + return nil, fmt.Errorf("failed to get CA certificate: %s", err) + } + err = copyToContainer(docker, containerID, "/ca/ca.crt", certBytes) + if err != nil { + return nil, fmt.Errorf("failed to copy CA certificate to container: %s", err) + } + certKeyBytes, err := cfg.CAPrivateKeyBytes() + if err != nil { + return nil, fmt.Errorf("failed to get CA key: %s", err) + } + err = copyToContainer(docker, containerID, "/ca/ca.key", certKeyBytes) + if err != nil { + return nil, fmt.Errorf("failed to copy CA key to container: %s", err) + } + err = docker.ContainerStart(ctx, containerID, types.ContainerStartOptions{}) if err != nil { return nil, err } - if debugLoggingEnabled { + if cfg.DebugLoggingEnabled { log.Printf("%s: Started container %s", contextStr, containerID) } var inspect types.ContainerJSON @@ -271,7 +268,7 @@ func deployImage( var lastErr error // Inspect health status of container to check it is up - stopTime := time.Now().Add(spawnHSTimeout) + stopTime := time.Now().Add(cfg.SpawnHSTimeout) iterCount := 0 if inspect.State.Health != nil { // If the container has a healthcheck, wait for it first @@ -333,13 +330,39 @@ func deployImage( if lastErr != nil { return d, fmt.Errorf("%s: failed to check server is up. %w", contextStr, lastErr) } else { - if debugLoggingEnabled { + if cfg.DebugLoggingEnabled { log.Printf("%s: Server is responding after %d iterations", contextStr, iterCount) } } return d, nil } +func copyToContainer(docker *client.Client, containerID, path string, data []byte) error { + // Create a fake/virtual file in memory that we can copy to the container + // via https://stackoverflow.com/a/52131297/796832 + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + err := tw.WriteHeader(&tar.Header{ + Name: path, + Mode: 0777, + Size: int64(len(data)), + }) + if err != nil { + return fmt.Errorf("copyToContainer: failed to write tarball header for %s: %v", path, err) + } + tw.Write([]byte(data)) + tw.Close() + + // Put our new fake file in the container volume + err = docker.CopyToContainer(context.Background(), containerID, "/", &buf, types.CopyToContainerOptions{ + AllowOverwriteDirWithFile: false, + }) + if err != nil { + return fmt.Errorf("copyToContainer: failed to copy: %s", err) + } + return nil +} + // RoundTripper is a round tripper that maps https://hs1 to the federation port of the container // e.g https://localhost:35352 type RoundTripper struct { diff --git a/internal/docker/deployment.go b/internal/docker/deployment.go index e8259cbe..1efbf819 100644 --- a/internal/docker/deployment.go +++ b/internal/docker/deployment.go @@ -5,6 +5,7 @@ import ( "time" "github.com/matrix-org/complement/internal/client" + "github.com/matrix-org/complement/internal/config" ) // Deployment is the complete instantiation of a Blueprint, with running containers @@ -15,7 +16,8 @@ type Deployment struct { // The name of the deployed blueprint BlueprintName string // A map of HS name to a HomeserverDeployment - HS map[string]HomeserverDeployment + HS map[string]HomeserverDeployment + Config *config.Complement } // HomeserverDeployment represents a running homeserver in a container. diff --git a/internal/docker/volumes.go b/internal/docker/volumes.go index 0e06ee5d..91ef8a3a 100644 --- a/internal/docker/volumes.go +++ b/internal/docker/volumes.go @@ -2,8 +2,6 @@ package docker import ( "context" - "os" - "path" "github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/volume" @@ -19,40 +17,6 @@ type Volume interface { Prepare(ctx context.Context, docker *client.Client, contextStr string) error } -type VolumeCA struct { - source string - typ mount.Type -} - -// Prepare the Certificate Authority volume. This is independent of the homeserver calling Prepare -// hence the contextual string is unused. -func (v *VolumeCA) Prepare(ctx context.Context, docker *client.Client, x string) error { - // Our CA cert is placed in the current working dir. - // We bind mount this directory to all homeserver containers. - cwd, err := os.Getwd() - if err != nil { - return err - } - caCertificateDirHost := path.Join(cwd, "ca") - if _, err := os.Stat(caCertificateDirHost); os.IsNotExist(err) { - err = os.Mkdir(caCertificateDirHost, 0770) - if err != nil { - return err - } - } - v.source = path.Join(cwd, "ca") - v.typ = mount.TypeBind - return nil -} - -func (v *VolumeCA) Mount() mount.Mount { - return mount.Mount{ - Type: v.typ, - Source: v.source, - Target: "/ca", - } -} - type VolumeAppService struct { source string } diff --git a/internal/federation/server.go b/internal/federation/server.go index 1ae61ab0..423e3823 100644 --- a/internal/federation/server.go +++ b/internal/federation/server.go @@ -9,9 +9,7 @@ import ( "crypto/x509/pkix" "encoding/json" "encoding/pem" - "errors" "fmt" - "io/ioutil" "math/big" "net" "net/http" @@ -26,6 +24,7 @@ import ( "github.com/matrix-org/util" "github.com/matrix-org/complement/internal/b" + "github.com/matrix-org/complement/internal/config" "github.com/matrix-org/complement/internal/docker" ) @@ -104,7 +103,7 @@ func NewServer(t *testing.T, deployment *docker.Deployment, opts ...func(*Server }) // generate certs and an http.Server - httpServer, certPath, keyPath, err := federationServer("name", srv.mux) + httpServer, certPath, keyPath, err := federationServer(deployment.Config, srv.mux) if err != nil { t.Fatalf("complement: unable to create federation server and certificates: %s", err.Error()) } @@ -411,125 +410,8 @@ func (s *Server) Listen() (cancel func()) { } } -// GetOrCreateCaCert is used to create the federation TLS cert. -// In addition, it is passed to homeserver containers to create TLS certs -// for the homeservers. -// This basically acts as a test only valid PKI. -func GetOrCreateCaCert() (*x509.Certificate, *rsa.PrivateKey, error) { - var tlsCACertPath, tlsCAKeyPath string - wd, err := os.Getwd() - if err != nil { - return nil, nil, err - } - tlsCACertPath = path.Join(wd, "ca", "ca.crt") - tlsCAKeyPath = path.Join(wd, "ca", "ca.key") - if _, err = os.Stat(path.Join(wd, "ca")); os.IsNotExist(err) { - err = os.Mkdir(path.Join(wd, "ca"), 0770) - if err != nil { - return nil, nil, err - } - } - - if _, err = os.Stat(tlsCACertPath); err == nil { - if _, err = os.Stat(tlsCAKeyPath); err == nil { - // We already created a CA cert, let's use that. - var dat []byte - dat, err = ioutil.ReadFile(tlsCACertPath) - if err != nil { - return nil, nil, err - } - block, _ := pem.Decode([]byte(dat)) - if block == nil || block.Type != "CERTIFICATE" { - return nil, nil, errors.New("ca.crt is not a valid pem encoded x509 cert") - } - var caCerts []*x509.Certificate - caCerts, err = x509.ParseCertificates(block.Bytes) - if err != nil { - return nil, nil, err - } - if len(caCerts) != 1 { - return nil, nil, errors.New("ca.crt contains none or more than one cert") - } - caCert := caCerts[0] - dat, err = ioutil.ReadFile(tlsCAKeyPath) - if err != nil { - return nil, nil, err - } - block, _ = pem.Decode([]byte(dat)) - if block == nil || block.Type != "RSA PRIVATE KEY" { - return nil, nil, errors.New("ca.key is not a valid pem encoded rsa private key") - } - var priv *rsa.PrivateKey - priv, err = x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - return nil, nil, err - } - return caCert, priv, nil - } - } - - // valid for 10 years - certificateDuration := time.Hour * 24 * 365 * 10 - priv, err := rsa.GenerateKey(rand.Reader, 4096) - if err != nil { - return nil, nil, err - } - notBefore := time.Now() - notAfter := notBefore.Add(certificateDuration) - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - return nil, nil, err - } - caCert := x509.Certificate{ - SerialNumber: serialNumber, - NotBefore: notBefore, - NotAfter: notAfter, - IsCA: true, - KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature | x509.KeyUsageCRLSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - Subject: pkix.Name{ - Organization: []string{"matrix.org"}, - Country: []string{"GB"}, - Province: []string{"London"}, - Locality: []string{"London"}, - StreetAddress: []string{"123 Street"}, - PostalCode: []string{"12345"}, - }, - } - - derBytes, err := x509.CreateCertificate(rand.Reader, &caCert, &caCert, &priv.PublicKey, priv) - if err != nil { - return nil, nil, err - } - certOut, err := os.Create(tlsCACertPath) - if err != nil { - return nil, nil, err - } - - defer certOut.Close() // nolint: errcheck - if err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { - return nil, nil, err - } - - keyOut, err := os.OpenFile(tlsCAKeyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) - if err != nil { - return nil, nil, err - } - defer keyOut.Close() // nolint: errcheck - err = pem.Encode(keyOut, &pem.Block{ - Type: "RSA PRIVATE KEY", - Bytes: x509.MarshalPKCS1PrivateKey(priv), - }) - if err != nil { - return nil, nil, err - } - return &caCert, priv, nil -} - // federationServer creates a federation server with the given handler -func federationServer(name string, h http.Handler) (*http.Server, string, string, error) { +func federationServer(cfg *config.Complement, h http.Handler) (*http.Server, string, string, error) { var derBytes []byte srv := &http.Server{ Addr: ":8448", @@ -573,13 +455,8 @@ func federationServer(name string, h http.Handler) (*http.Server, string, string template.DNSNames = append(template.DNSNames, host) } - var ca *x509.Certificate - var caPrivKey *rsa.PrivateKey - ca, caPrivKey, err = GetOrCreateCaCert() - if err != nil { - return nil, "", "", err - } - derBytes, err = x509.CreateCertificate(rand.Reader, &template, ca, &priv.PublicKey, caPrivKey) + // derive a new certificate from the base complement one + derBytes, err = x509.CreateCertificate(rand.Reader, &template, cfg.CACertificate, &priv.PublicKey, cfg.CAPrivateKey) if err != nil { return nil, "", "", err } diff --git a/tests/csapi/main_test.go b/tests/csapi/main_test.go index 829108c3..78c901b7 100644 --- a/tests/csapi/main_test.go +++ b/tests/csapi/main_test.go @@ -15,7 +15,6 @@ import ( "github.com/matrix-org/complement/internal/b" "github.com/matrix-org/complement/internal/config" "github.com/matrix-org/complement/internal/docker" - "github.com/matrix-org/complement/internal/federation" ) var namespaceCounter uint64 @@ -40,16 +39,6 @@ func TestMain(m *testing.M) { // remove any old images/containers/networks in case we died horribly before builder.Cleanup() - if os.Getenv("COMPLEMENT_CA") == "true" { - log.Printf("Running with Complement CA") - // make sure CA certs are generated - _, _, err = federation.GetOrCreateCaCert() - if err != nil { - fmt.Printf("Error: %s", err) - os.Exit(1) - } - } - // we use GMSL which uses logrus by default. We don't want those logs in our test output unless they are Serious. logrus.SetLevel(logrus.ErrorLevel) diff --git a/tests/main_test.go b/tests/main_test.go index 8f5a3204..d9e3a65b 100644 --- a/tests/main_test.go +++ b/tests/main_test.go @@ -15,7 +15,6 @@ import ( "github.com/matrix-org/complement/internal/b" "github.com/matrix-org/complement/internal/config" "github.com/matrix-org/complement/internal/docker" - "github.com/matrix-org/complement/internal/federation" ) var namespaceCounter uint64 @@ -39,16 +38,6 @@ func TestMain(m *testing.M) { // remove any old images/containers/networks in case we died horribly before builder.Cleanup() - if os.Getenv("COMPLEMENT_CA") == "true" { - log.Printf("Running with Complement CA") - // make sure CA certs are generated - _, _, err = federation.GetOrCreateCaCert() - if err != nil { - fmt.Printf("Error: %s", err) - os.Exit(1) - } - } - // we use GMSL which uses logrus by default. We don't want those logs in our test output unless they are Serious. logrus.SetLevel(logrus.ErrorLevel)