github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/providers/security/puppetsec/puppet_security.go (about)

     1  // Copyright (c) 2020-2022, R.I. Pienaar and the Choria Project contributors
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  // Package puppetsec provides a Puppet compatable Security Provider
     6  //
     7  // The provider supports enrolling into a Puppet CA by creating a
     8  // key and csr, sending it to the PuppetCA and waiting for it to
     9  // be signed and later it will download the certificate once signed
    10  package puppetsec
    11  
    12  import (
    13  	"bytes"
    14  	"context"
    15  	"crypto/rand"
    16  	"crypto/rsa"
    17  	"crypto/sha256"
    18  	"crypto/tls"
    19  	"crypto/x509"
    20  	"crypto/x509/pkix"
    21  	"encoding/asn1"
    22  	"encoding/pem"
    23  	"errors"
    24  	"fmt"
    25  	"io"
    26  	"net/http"
    27  	"os"
    28  	"path/filepath"
    29  	"time"
    30  
    31  	"github.com/choria-io/go-choria/inter"
    32  	"github.com/choria-io/go-choria/internal/util"
    33  	"github.com/choria-io/go-choria/tlssetup"
    34  
    35  	"github.com/choria-io/go-choria/providers/security/filesec"
    36  	"github.com/choria-io/go-choria/srvcache"
    37  	"github.com/sirupsen/logrus"
    38  )
    39  
    40  // Resolver provides DNS lookup facilities
    41  type Resolver interface {
    42  	QuerySrvRecords(records []string) (srvcache.Servers, error)
    43  }
    44  
    45  // BuildInfoProvider provides info about the build
    46  type BuildInfoProvider interface {
    47  	ClientIdentitySuffix() string
    48  }
    49  
    50  // PuppetSecurity implements SecurityProvider reusing AIO Puppet settings
    51  // it supports enrollment the same way `puppet agent --waitforcert 10` does
    52  type PuppetSecurity struct {
    53  	res  Resolver
    54  	conf *Config
    55  	log  *logrus.Entry
    56  
    57  	fsec *filesec.FileSecurity
    58  }
    59  
    60  // Config is the configuration for PuppetSecurity
    61  type Config struct {
    62  	// Identity when not empty will force the identity to be used for validations etc
    63  	Identity string
    64  
    65  	// SSLDir is the directory where Puppet stores it's SSL
    66  	SSLDir string
    67  
    68  	// PrivilegedUsers is a list of regular expressions that identity privilged users
    69  	PrivilegedUsers []string
    70  
    71  	// AllowList is a list of regular expressions that identity valid users to allow in
    72  	AllowList []string
    73  
    74  	// DisableTLSVerify disables TLS verify in HTTP clients etc
    75  	DisableTLSVerify bool
    76  
    77  	// PuppetCAHost is the hostname of the PuppetCA
    78  	PuppetCAHost string
    79  
    80  	// PuppetCAPort is the port of the PuppetCA
    81  	PuppetCAPort int
    82  
    83  	// DisableSRV prevents SRV lookups
    84  	DisableSRV bool
    85  
    86  	// Is a URL where a remote signer is running
    87  	RemoteSignerURL string
    88  
    89  	// RemoteSignerTokenFile is a file with a token for access to the remote signer
    90  	RemoteSignerTokenFile string
    91  
    92  	// RemoteSignerTokenEnvironment is an environment variable that will hold the signer token
    93  	RemoteSignerTokenEnvironment string
    94  
    95  	useFakeUID bool
    96  	fakeUID    int
    97  
    98  	// TLSConfig is the shared TLS configuration
    99  	TLSConfig *tlssetup.Config
   100  
   101  	// AltNames are additional names to add to the CSR
   102  	AltNames []string
   103  
   104  	// IdentitySuffix is the suffix to append to user names when creating certnames and identities
   105  	IdentitySuffix string
   106  
   107  	// RemoteSigner is the signer used to sign requests using a remote like AAA Service
   108  	RemoteSigner inter.RequestSigner
   109  }
   110  
   111  // New creates a new instance of the Puppet Security Provider
   112  func New(opts ...Option) (*PuppetSecurity, error) {
   113  	p := &PuppetSecurity{}
   114  
   115  	for _, opt := range opts {
   116  		err := opt(p)
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  	}
   121  
   122  	if p.conf == nil {
   123  		return nil, errors.New("configuration not given")
   124  	}
   125  
   126  	if p.log == nil {
   127  		return nil, errors.New("logger not given")
   128  	}
   129  
   130  	if p.conf.Identity == "" {
   131  		return nil, errors.New("identity could not be determine automatically via Choria or was not supplied")
   132  	}
   133  
   134  	return p, p.reinit()
   135  }
   136  
   137  func (s *PuppetSecurity) reinit() error {
   138  	var err error
   139  
   140  	fc := filesec.Config{
   141  		AllowList:                  s.conf.AllowList,
   142  		DisableTLSVerify:           s.conf.DisableTLSVerify,
   143  		PrivilegedUsers:            s.conf.PrivilegedUsers,
   144  		CA:                         s.caPath(),
   145  		Certificate:                s.publicCertPath(),
   146  		Key:                        s.privateKeyPath(),
   147  		Identity:                   s.conf.Identity,
   148  		RemoteSignerURL:            s.conf.RemoteSignerURL,
   149  		RemoteSignerTokenFile:      s.conf.RemoteSignerTokenFile,
   150  		TLSConfig:                  s.conf.TLSConfig,
   151  		IdentitySuffix:             s.conf.IdentitySuffix,
   152  		BackwardCompatVerification: true,
   153  		RemoteSigner:               s.conf.RemoteSigner,
   154  	}
   155  
   156  	s.fsec, err = filesec.New(filesec.WithConfig(&fc), filesec.WithLog(s.log))
   157  	if err != nil {
   158  		return err
   159  	}
   160  
   161  	if fc.BackwardCompatVerification {
   162  		s.log.Debugf("Puppet security system requesting legacy TLS support")
   163  	} else {
   164  		s.log.Debugf("Puppet security system supporting only new certificates")
   165  	}
   166  
   167  	return nil
   168  }
   169  
   170  func (s *PuppetSecurity) BackingTechnology() inter.SecurityTechnology {
   171  	return s.fsec.BackingTechnology()
   172  }
   173  
   174  // Provider reports the name of the security provider
   175  func (s *PuppetSecurity) Provider() string {
   176  	return "puppet"
   177  }
   178  
   179  func (s *PuppetSecurity) TokenBytes() ([]byte, error) {
   180  	return nil, fmt.Errorf("tokens not available for puppet security provider")
   181  }
   182  
   183  // Enroll sends a CSR to the PuppetCA and wait for it to be signed
   184  func (s *PuppetSecurity) Enroll(ctx context.Context, wait time.Duration, cb func(digest string, try int)) error {
   185  	if s.privateKeyExists() && s.caExists() && s.publicCertExists() {
   186  		return errors.New("already have all files needed for SSL operations")
   187  	}
   188  
   189  	err := s.createSSLDirectories()
   190  	if err != nil {
   191  		return fmt.Errorf("could not initialize ssl directories: %s", err)
   192  	}
   193  
   194  	var key *rsa.PrivateKey
   195  
   196  	if s.privateKeyExists() {
   197  		s.log.Debugf("Loading existing private key for %s", s.Identity())
   198  		key, err = s.readPrivateKey()
   199  		if err != nil {
   200  			return fmt.Errorf("could not read private key for %s: %s", s.Identity(), err)
   201  		}
   202  	} else {
   203  		s.log.Debugf("Creating a new Private Key %s", s.Identity())
   204  
   205  		key, err = s.writePrivateKey()
   206  		if err != nil {
   207  			return fmt.Errorf("could not write a new private key: %s", err)
   208  		}
   209  	}
   210  
   211  	if !s.caExists() {
   212  		s.log.Debug("Fetching CA")
   213  
   214  		err = s.fetchCA()
   215  		if err != nil {
   216  			return fmt.Errorf("could not fetch CA: %s", err)
   217  		}
   218  	}
   219  
   220  	previousCSR := s.csrExists()
   221  	var digest string
   222  
   223  	if !previousCSR {
   224  		s.log.Debugf("Creating a new CSR for %s", s.Identity())
   225  
   226  		digest, err = s.writeCSR(key, s.Identity(), "choria.io")
   227  		if err != nil {
   228  			return fmt.Errorf("could not write CSR: %s", err)
   229  		}
   230  	}
   231  
   232  	if !s.publicCertExists() {
   233  		s.log.Debug("Submitting CSR to the PuppetCA")
   234  
   235  		err = s.submitCSR()
   236  		if err != nil {
   237  			if previousCSR {
   238  				s.log.Warnf("Submitting CSR failed, ignoring failure as this might be a continuation of a previous attempts: %s", err)
   239  			} else {
   240  				return fmt.Errorf("could not submit csr: %s", err)
   241  			}
   242  		}
   243  	}
   244  
   245  	timeout := time.NewTimer(wait).C
   246  	ticks := time.NewTicker(10 * time.Second).C
   247  
   248  	complete := make(chan int, 2)
   249  
   250  	attempt := 1
   251  
   252  	fetcher := func() {
   253  		cb(digest, attempt)
   254  		attempt++
   255  
   256  		err := s.fetchCert()
   257  		if err != nil {
   258  			s.log.Debugf("Error while fetching cert on attempt %d: %s", attempt-1, err)
   259  			return
   260  		}
   261  
   262  		complete <- 1
   263  	}
   264  
   265  	fetcher()
   266  
   267  	for {
   268  		select {
   269  		case <-ctx.Done():
   270  			return fmt.Errorf("interrupted")
   271  		case <-timeout:
   272  			return fmt.Errorf("timed out waiting for a certificate")
   273  		case <-complete:
   274  			return nil
   275  		case <-ticks:
   276  			fetcher()
   277  		}
   278  	}
   279  }
   280  
   281  // Validate determines if the node represents a valid SSL configuration
   282  func (s *PuppetSecurity) Validate() ([]string, bool) {
   283  	errors := []string{}
   284  
   285  	ferrs, _ := s.fsec.Validate()
   286  	errors = append(errors, ferrs...)
   287  
   288  	return errors, len(errors) == 0
   289  }
   290  
   291  // RemoteSignRequest signs a choria request using a remote signer and returns a secure request
   292  func (s *PuppetSecurity) RemoteSignRequest(ctx context.Context, str []byte) (signed []byte, err error) {
   293  	return s.fsec.RemoteSignRequest(ctx, str)
   294  }
   295  
   296  func (s *PuppetSecurity) IsRemoteSigning() bool {
   297  	return s.fsec.IsRemoteSigning()
   298  }
   299  
   300  // ChecksumBytes calculates a sha256 checksum for data
   301  func (s *PuppetSecurity) ChecksumBytes(data []byte) []byte {
   302  	return s.fsec.ChecksumBytes(data)
   303  }
   304  
   305  // SignBytes signs a message using a SHA256 PKCS1v15 protocol
   306  func (s *PuppetSecurity) SignBytes(str []byte) ([]byte, error) {
   307  	return s.fsec.SignBytes(str)
   308  }
   309  
   310  // VerifyByteSignature verify that dat matches signature sig made by the key, if pub cert is empty the active public key will be used
   311  func (s *PuppetSecurity) VerifySignatureBytes(dat []byte, sig []byte, public ...[]byte) (should bool, signer string) {
   312  	return s.fsec.VerifySignatureBytes(dat, sig, public...)
   313  }
   314  
   315  // CallerName creates a choria like caller name in the form of choria=identity
   316  func (s *PuppetSecurity) CallerName() string {
   317  	return s.fsec.CallerName()
   318  }
   319  
   320  // CallerIdentity extracts the identity from a choria like caller name in the form of choria=identity
   321  func (s *PuppetSecurity) CallerIdentity(caller string) (string, error) {
   322  	return s.fsec.CallerIdentity(caller)
   323  }
   324  
   325  // ShouldAllowCaller verifies the public data
   326  func (s *PuppetSecurity) ShouldAllowCaller(name string, callers ...[]byte) (privileged bool, err error) {
   327  	return s.fsec.ShouldAllowCaller(name, callers...)
   328  }
   329  
   330  // VerifyCertificate verifies a certificate is signed with the configured CA and if
   331  // name is not "" that it matches the name given
   332  func (s *PuppetSecurity) VerifyCertificate(certpem []byte, name string) error {
   333  	return s.fsec.VerifyCertificate(certpem, name)
   334  }
   335  
   336  // PublicCertBytes retrieves pem data in textual form for the public certificate of the current identity
   337  func (s *PuppetSecurity) PublicCertBytes() ([]byte, error) {
   338  	return s.fsec.PublicCertBytes()
   339  }
   340  
   341  // PublicCert is the parsed public certificate
   342  func (s *PuppetSecurity) PublicCert() (*x509.Certificate, error) {
   343  	return s.fsec.PublicCert()
   344  }
   345  
   346  // Identity determines the choria certname
   347  func (s *PuppetSecurity) Identity() string {
   348  	return s.conf.Identity
   349  }
   350  
   351  // TLSConfig creates a TLS configuration for use by NATS, HTTPS etc
   352  func (s *PuppetSecurity) TLSConfig() (*tls.Config, error) {
   353  	return s.fsec.TLSConfig()
   354  }
   355  
   356  // ClientTLSConfig creates a TLS configuration for use by NATS, HTTPS etc
   357  func (s *PuppetSecurity) ClientTLSConfig() (*tls.Config, error) {
   358  	return s.fsec.ClientTLSConfig()
   359  }
   360  
   361  // SSLContext creates a SSL context loaded with our certs and ca
   362  func (s *PuppetSecurity) SSLContext() (*http.Transport, error) {
   363  	return s.fsec.SSLContext()
   364  }
   365  
   366  func (s *PuppetSecurity) caPath() string {
   367  	return filepath.FromSlash(filepath.Join(s.sslDir(), "certs", "ca.pem"))
   368  }
   369  
   370  func (s *PuppetSecurity) privateKeyDir() string {
   371  	return filepath.FromSlash(filepath.Join(s.sslDir(), "private_keys"))
   372  }
   373  
   374  func (s *PuppetSecurity) privateKeyPath() string {
   375  	return filepath.FromSlash(filepath.Join(s.privateKeyDir(), fmt.Sprintf("%s.pem", s.Identity())))
   376  }
   377  
   378  func (s *PuppetSecurity) createSSLDirectories() error {
   379  	ssl := s.sslDir()
   380  
   381  	err := os.MkdirAll(ssl, 0771)
   382  	if err != nil {
   383  		return err
   384  	}
   385  
   386  	for _, dir := range []string{"certificate_requests", "certs", "public_keys"} {
   387  		path := filepath.FromSlash(filepath.Join(ssl, dir))
   388  		err = os.MkdirAll(path, 0755)
   389  		if err != nil {
   390  			return err
   391  		}
   392  	}
   393  
   394  	for _, dir := range []string{"private_keys", "private"} {
   395  		path := filepath.FromSlash(filepath.Join(ssl, dir))
   396  		err = os.MkdirAll(path, 0750)
   397  		if err != nil {
   398  			return err
   399  		}
   400  	}
   401  
   402  	return nil
   403  }
   404  
   405  func (s *PuppetSecurity) csrPath() string {
   406  	return filepath.FromSlash((filepath.Join(s.sslDir(), "certificate_requests", fmt.Sprintf("%s.pem", s.Identity()))))
   407  }
   408  
   409  func (s *PuppetSecurity) publicCertPath() string {
   410  	return filepath.FromSlash((filepath.Join(s.sslDir(), "certs", fmt.Sprintf("%s.pem", s.Identity()))))
   411  }
   412  
   413  func (s *PuppetSecurity) sslDir() string {
   414  	return s.conf.SSLDir
   415  }
   416  
   417  func (s *PuppetSecurity) writeCSR(key *rsa.PrivateKey, cn string, ou string) (string, error) {
   418  	if s.csrExists() {
   419  		return "", fmt.Errorf("a certificate request already exist for %s", s.Identity())
   420  	}
   421  
   422  	path := s.csrPath()
   423  
   424  	subj := pkix.Name{
   425  		CommonName:         cn,
   426  		OrganizationalUnit: []string{ou},
   427  	}
   428  
   429  	asn1Subj, err := asn1.Marshal(subj.ToRDNSequence())
   430  	if err != nil {
   431  		return "", fmt.Errorf("could not create subject: %s", err)
   432  	}
   433  
   434  	template := x509.CertificateRequest{
   435  		RawSubject:         asn1Subj,
   436  		SignatureAlgorithm: x509.SHA256WithRSA,
   437  	}
   438  
   439  	template.DNSNames = append(template.DNSNames, s.conf.AltNames...)
   440  
   441  	csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, key)
   442  	if err != nil {
   443  		return "", fmt.Errorf("could not create csr: %s", err)
   444  	}
   445  
   446  	csr, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0640)
   447  	if err != nil {
   448  		return "", fmt.Errorf("could not open csr %s for writing: %s", path, err)
   449  	}
   450  	defer csr.Close()
   451  
   452  	err = pem.Encode(csr, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes})
   453  	if err != nil {
   454  		return "", fmt.Errorf("could not encode csr into %s: %s", path, err)
   455  	}
   456  
   457  	return fmt.Sprintf("%x", sha256.Sum256(csrBytes)), nil
   458  }
   459  
   460  func (s *PuppetSecurity) puppetCA() srvcache.Server {
   461  	found := srvcache.NewServer(s.conf.PuppetCAHost, s.conf.PuppetCAPort, "https")
   462  
   463  	if s.conf.DisableSRV || s.res == nil {
   464  		return found
   465  	}
   466  
   467  	servers, err := s.res.QuerySrvRecords([]string{"_x-puppet-ca._tcp", "_x-puppet._tcp"})
   468  	if err != nil {
   469  		s.log.Warnf("Could not resolve Puppet CA SRV records: %s", err)
   470  		return found
   471  	}
   472  
   473  	if servers.Count() == 0 {
   474  		return found
   475  	}
   476  
   477  	found = servers.Servers()[0]
   478  
   479  	if found.Scheme() == "" {
   480  		found.SetScheme("https")
   481  	}
   482  
   483  	return found
   484  }
   485  
   486  func (s *PuppetSecurity) fetchCert() error {
   487  	if s.publicCertExists() {
   488  		return nil
   489  	}
   490  
   491  	server := s.puppetCA()
   492  	url := fmt.Sprintf("%s://%s:%d/puppet-ca/v1/certificate/%s?environment=production", server.Scheme(), server.Host(), server.Port(), s.Identity())
   493  
   494  	req, err := http.NewRequest("GET", url, nil)
   495  	if err != nil {
   496  		return fmt.Errorf("could not create http request: %s", err)
   497  	}
   498  
   499  	req.Header.Set("Content-Type", "text/plain")
   500  	req.Header.Set("User-Agent", "Choria Orchestrator - http://choria.io")
   501  
   502  	client, err := s.HTTPClient(server.Scheme() == "https")
   503  	if err != nil {
   504  		return fmt.Errorf("could not set up HTTP connection: %s", err)
   505  	}
   506  
   507  	resp, err := client.Do(req)
   508  	if err != nil {
   509  		return fmt.Errorf("could not fetch certificate: %s", err)
   510  	}
   511  	defer resp.Body.Close()
   512  
   513  	if resp.StatusCode != 200 {
   514  		return fmt.Errorf("could not fetch certificate: %d", resp.StatusCode)
   515  	}
   516  
   517  	body, err := io.ReadAll(resp.Body)
   518  	if err != nil {
   519  		return fmt.Errorf("could not read response body: %s", err)
   520  	}
   521  
   522  	err = os.WriteFile(s.publicCertPath(), body, 0644)
   523  	if err != nil {
   524  		return err
   525  	}
   526  
   527  	return nil
   528  }
   529  
   530  func (s *PuppetSecurity) fetchCA() error {
   531  	if s.caExists() {
   532  		return nil
   533  	}
   534  
   535  	server := s.puppetCA()
   536  	url := fmt.Sprintf("%s://%s:%d/puppet-ca/v1/certificate/ca?environment=production", server.Scheme(), server.Host(), server.Port())
   537  
   538  	// specifically disabling verification as at this point we do not have
   539  	// the CA needed to do verification, there's no choice in the matter
   540  	// really and this is just how its designed to work
   541  	tr := &http.Transport{
   542  		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
   543  	}
   544  
   545  	client := &http.Client{Transport: tr}
   546  
   547  	resp, err := client.Get(url)
   548  	if err != nil {
   549  		return err
   550  	}
   551  	defer resp.Body.Close()
   552  
   553  	body, err := io.ReadAll(resp.Body)
   554  	if err != nil {
   555  		return fmt.Errorf("could not read response body: %s", err)
   556  	}
   557  
   558  	if resp.StatusCode != 200 {
   559  		return errors.New(string(body))
   560  	}
   561  
   562  	err = os.WriteFile(s.caPath(), body, 0644)
   563  	if err != nil {
   564  		return err
   565  	}
   566  
   567  	return nil
   568  }
   569  
   570  func (s *PuppetSecurity) submitCSR() error {
   571  	csr, err := s.csrTXT()
   572  	if err != nil {
   573  		return fmt.Errorf("could not read CSR: %s", err)
   574  	}
   575  
   576  	server := s.puppetCA()
   577  
   578  	url := fmt.Sprintf("%s://%s:%d/puppet-ca/v1/certificate_request/%s?environment=production", server.Scheme(), server.Host(), server.Port(), s.Identity())
   579  
   580  	req, err := http.NewRequest("PUT", url, bytes.NewBuffer(csr))
   581  	if err != nil {
   582  		return fmt.Errorf("could not create http request: %s", err)
   583  	}
   584  
   585  	req.Header.Set("Content-Type", "text/plain")
   586  	req.Header.Set("User-Agent", "Choria Orchestrator - http://choria.io")
   587  
   588  	req.Host = server.Host()
   589  
   590  	client, err := s.HTTPClient(server.Scheme() == "https")
   591  	if err != nil {
   592  		return fmt.Errorf("could not set up HTTP connection: %s", err)
   593  	}
   594  
   595  	resp, err := client.Do(req)
   596  	if err != nil {
   597  		return fmt.Errorf("could not send CSR: %s", err)
   598  	}
   599  	defer resp.Body.Close()
   600  
   601  	if resp.StatusCode == 200 {
   602  		return nil
   603  	}
   604  
   605  	body, err := io.ReadAll(resp.Body)
   606  	if err != nil {
   607  		return fmt.Errorf("could not read response body: %s", err)
   608  	}
   609  
   610  	if len(body) > 0 {
   611  		return fmt.Errorf("could not send CSR to %s://%s:%d: %s: %s", server.Scheme(), server.Host(), server.Port(), resp.Status, string(body))
   612  	}
   613  
   614  	return fmt.Errorf("could not send CSR to %s://%s:%d: %s", server.Scheme(), server.Host(), server.Port(), resp.Status)
   615  }
   616  
   617  // HTTPClient creates a standard HTTP client with optional security, it will
   618  // be set to use the CA and client certs for auth. servername should match the
   619  // remote hosts name for SNI
   620  func (s *PuppetSecurity) HTTPClient(secure bool) (*http.Client, error) {
   621  	return s.fsec.HTTPClient(secure)
   622  }
   623  
   624  func (s *PuppetSecurity) csrTXT() ([]byte, error) {
   625  	return os.ReadFile(s.csrPath())
   626  }
   627  
   628  func (s *PuppetSecurity) readPrivateKey() (*rsa.PrivateKey, error) {
   629  	if !s.privateKeyExists() {
   630  		return nil, fmt.Errorf("key not found in %s", s.privateKeyPath())
   631  	}
   632  
   633  	pd, err := os.ReadFile(s.privateKeyPath())
   634  	if err != nil {
   635  		return nil, err
   636  	}
   637  
   638  	privPem, _ := pem.Decode(pd)
   639  	if privPem.Type != "RSA PRIVATE KEY" {
   640  		return nil, fmt.Errorf("key file %s did not contain a private key", s.privateKeyPath())
   641  	}
   642  
   643  	parsedKey, err := x509.ParsePKCS1PrivateKey(privPem.Bytes)
   644  	if err != nil {
   645  		return nil, err
   646  	}
   647  
   648  	return parsedKey, nil
   649  }
   650  
   651  func (s *PuppetSecurity) writePrivateKey() (*rsa.PrivateKey, error) {
   652  	if s.privateKeyExists() {
   653  		return nil, fmt.Errorf("a private key already exist for %s", s.Identity())
   654  	}
   655  
   656  	key, err := rsa.GenerateKey(rand.Reader, 2048)
   657  	if err != nil {
   658  		return nil, fmt.Errorf("could not generate rsa key: %s", err)
   659  	}
   660  
   661  	pemdata := pem.EncodeToMemory(
   662  		&pem.Block{
   663  			Type:  "RSA PRIVATE KEY",
   664  			Bytes: x509.MarshalPKCS1PrivateKey(key),
   665  		},
   666  	)
   667  
   668  	err = os.WriteFile(s.privateKeyPath(), pemdata, 0640)
   669  	if err != nil {
   670  		return nil, fmt.Errorf("could not write private key: %s", err)
   671  	}
   672  
   673  	return key, nil
   674  }
   675  
   676  func (s *PuppetSecurity) csrExists() bool {
   677  	return util.FileExist(s.csrPath())
   678  }
   679  
   680  func (s *PuppetSecurity) privateKeyExists() bool {
   681  	return util.FileExist(s.privateKeyPath())
   682  }
   683  
   684  func (s *PuppetSecurity) publicCertExists() bool {
   685  	return util.FileExist(s.publicCertPath())
   686  }
   687  
   688  func (s *PuppetSecurity) caExists() bool {
   689  	return util.FileExist(s.caPath())
   690  }
   691  
   692  func (s *PuppetSecurity) ShouldSignReplies() bool { return false }