github.com/jcarley/cli@v0.0.0-20180201210820-966d90434c30/commands/certs/contract.go (about)

     1  package certs
     2  
     3  import (
     4  	"github.com/Sirupsen/logrus"
     5  	"github.com/daticahealth/cli/commands/services"
     6  	"github.com/daticahealth/cli/commands/ssl"
     7  	"github.com/daticahealth/cli/config"
     8  	"github.com/daticahealth/cli/lib/auth"
     9  	"github.com/daticahealth/cli/lib/prompts"
    10  	"github.com/daticahealth/cli/models"
    11  	"github.com/jault3/mow.cli"
    12  )
    13  
    14  // Cmd is the contract between the user and the CLI. This specifies the command
    15  // name, arguments, and required/optional arguments and flags for the command.
    16  var Cmd = models.Command{
    17  	Name:      "certs",
    18  	ShortHelp: "Manage your SSL certificates and domains",
    19  	LongHelp:  "The <code>certs</code> command gives access to certificate and private key management for public facing services. The certs command cannot be run directly but has subcommands.",
    20  	CmdFunc: func(settings *models.Settings) func(cmd *cli.Cmd) {
    21  		return func(cmd *cli.Cmd) {
    22  			cmd.CommandLong(CreateSubCmd.Name, CreateSubCmd.ShortHelp, CreateSubCmd.LongHelp, CreateSubCmd.CmdFunc(settings))
    23  			cmd.CommandLong(ListSubCmd.Name, ListSubCmd.ShortHelp, ListSubCmd.LongHelp, ListSubCmd.CmdFunc(settings))
    24  			cmd.CommandLong(RmSubCmd.Name, RmSubCmd.ShortHelp, RmSubCmd.LongHelp, RmSubCmd.CmdFunc(settings))
    25  			cmd.CommandLong(UpdateSubCmd.Name, UpdateSubCmd.ShortHelp, UpdateSubCmd.LongHelp, UpdateSubCmd.CmdFunc(settings))
    26  		}
    27  	},
    28  }
    29  
    30  var CreateSubCmd = models.Command{
    31  	Name:      "create",
    32  	ShortHelp: "Create a new domain with an SSL certificate and private key or create a Let's Encrypt certificate",
    33  	LongHelp: "<code>certs create</code> allows you to upload an SSL certificate and private key which can be used to secure your public facing code service. " +
    34  		"Alternatively, you may opt to create a Let's Encrypt certificate. When creating a Let's Encrypt certificate, you only need to provide the certificate name along with the \"-l\" flag. " +
    35  		"Let's Encrypt certificates are issued asynchronously and may not be available immediately. Use the certs list command to check on the issuance status. " +
    36  		"Once issued, Let's Encrypt certificates automatically renew before expiring. " +
    37  		"Cert creation can be done at any time, even after environment provisioning, but must be done before creating a site. " +
    38  		"When uploading a custom cert, the CLI will check to ensure the certificate and private key match. If you are using a self signed cert, pass in the <code>-s</code> flag and the hostname check will be skipped. " +
    39  		"Datica requires that your certificate file include your own certificate, intermediate certificates, and the root certificate in that order. " +
    40  		"If you only include your certificate, the CLI will attempt to resolve this and fetch intermediate and root certificates for you. " +
    41  		"It is advised that you create a full chain before running this command as the <code>-r</code> flag is accomplished on a \"best effort\" basis.\n\n" +
    42  		"Here are a few sample commands\n\n" +
    43  		"<pre>\ndatica -E \"<your_env_name>\" certs create wildcard_mysitecom ~/path/to/cert.pem ~/path/to/priv.key\n" +
    44  		"datica -E \"<your_env_name>\" certs create my.site.com --lets-encrypt\n</pre>",
    45  	CmdFunc: func(settings *models.Settings) func(cmd *cli.Cmd) {
    46  		return func(subCmd *cli.Cmd) {
    47  			name := subCmd.StringArg("NAME", "", "The name of this SSL certificate plus private key pair")
    48  			pubKeyPath := subCmd.StringArg("PUBLIC_KEY_PATH", "", "The path to a public key file in PEM format")
    49  			privKeyPath := subCmd.StringArg("PRIVATE_KEY_PATH", "", "The path to an unencrypted private key file in PEM format")
    50  			downStream := subCmd.StringOpt("down-stream", "service_proxy", "The down-stream service the cert belongs to.")
    51  			selfSigned := subCmd.BoolOpt("s self-signed", false, "Whether or not the given SSL certificate and private key are self signed")
    52  			resolve := subCmd.BoolOpt("r resolve", true, "Whether or not to attempt to automatically resolve incomplete SSL certificate issues")
    53  			letsEncrypt := subCmd.BoolOpt("l lets-encrypt", false, "Whether or not this is a Let's Encrypt certificate")
    54  			subCmd.Action = func() {
    55  				if _, err := auth.New(settings, prompts.New()).Signin(); err != nil {
    56  					logrus.Fatal(err.Error())
    57  				}
    58  				if err := config.CheckRequiredAssociation(settings); err != nil {
    59  					logrus.Fatal(err.Error())
    60  				}
    61  				err := CmdCreate(*name, *pubKeyPath, *privKeyPath, *downStream, *selfSigned, *resolve, *letsEncrypt, New(settings), services.New(settings), ssl.New(settings))
    62  				if err != nil {
    63  					logrus.Fatal(err.Error())
    64  				}
    65  			}
    66  			subCmd.Spec = "NAME ((PUBLIC_KEY_PATH PRIVATE_KEY_PATH [-s] [-r]) | -l) [--down-stream]"
    67  		}
    68  	},
    69  }
    70  
    71  var ListSubCmd = models.Command{
    72  	Name:      "list",
    73  	ShortHelp: "List all existing domains that have SSL certificate and private key pairs",
    74  	LongHelp: "<code>certs list</code> lists all of the available certs you have created on your environment. " +
    75  		"The displayed names are the names that should be used as the <code>CERT_NAME</code> parameter in the sites create command. " +
    76  		"If any certs are Let's Encrypt certs, the issuance status will also be shown. " +
    77  		"Here is a sample command\n\n" +
    78  		"<pre>\ndatica -E \"<your_env_name>\" certs list\n</pre>",
    79  	CmdFunc: func(settings *models.Settings) func(cmd *cli.Cmd) {
    80  		return func(subCmd *cli.Cmd) {
    81  			downStream := subCmd.StringOpt("down-stream", "service_proxy", "The down-stream service to list certs for.")
    82  			subCmd.Action = func() {
    83  				if _, err := auth.New(settings, prompts.New()).Signin(); err != nil {
    84  					logrus.Fatal(err.Error())
    85  				}
    86  				if err := config.CheckRequiredAssociation(settings); err != nil {
    87  					logrus.Fatal(err.Error())
    88  				}
    89  				err := CmdList(New(settings), services.New(settings), *downStream)
    90  				if err != nil {
    91  					logrus.Fatal(err.Error())
    92  				}
    93  			}
    94  			subCmd.Spec = "[--down-stream]"
    95  		}
    96  	},
    97  }
    98  
    99  var RmSubCmd = models.Command{
   100  	Name:      "rm",
   101  	ShortHelp: "Remove an existing domain and its associated SSL certificate and private key pair",
   102  	LongHelp: "<code>certs rm</code> allows you to delete old certificate and private key pairs. Only certs that are not in use by a site can be deleted. Here is a sample command\n\n" +
   103  		"<pre>\ndatica -E \"<your_env_name>\" certs rm mywebsite.com\n</pre>",
   104  	CmdFunc: func(settings *models.Settings) func(cmd *cli.Cmd) {
   105  		return func(subCmd *cli.Cmd) {
   106  			name := subCmd.StringArg("NAME", "", "The name of the certificate to remove")
   107  			downStream := subCmd.StringOpt("down-stream", "service_proxy", "The down-stream service the cert belongs to.")
   108  			subCmd.Action = func() {
   109  				if _, err := auth.New(settings, prompts.New()).Signin(); err != nil {
   110  					logrus.Fatal(err.Error())
   111  				}
   112  				if err := config.CheckRequiredAssociation(settings); err != nil {
   113  					logrus.Fatal(err.Error())
   114  				}
   115  				err := CmdRm(*name, New(settings), services.New(settings), *downStream)
   116  				if err != nil {
   117  					logrus.Fatal(err.Error())
   118  				}
   119  			}
   120  			subCmd.Spec = "NAME [--down-stream]"
   121  		}
   122  	},
   123  }
   124  
   125  var UpdateSubCmd = models.Command{
   126  	Name:      "update",
   127  	ShortHelp: "Update the SSL certificate and private key pair for an existing domain",
   128  	LongHelp: "<code>certs update</code> works nearly identical to the certs create command. " +
   129  		"All rules regarding self signed certs and certificate resolution from the <code>certs create</code> command apply to the <code>certs update</code> command. " +
   130  		"Let's Encrypt certs cannot be updated since they are automatically renewed before expiring. " +
   131  		"This is useful for when your certificates have expired and you need to upload new ones. Update your certs and then redeploy your service_proxy. Here is a sample command\n\n" +
   132  		"<pre>\ndatica -E \"<your_env_name>\" certs update mywebsite.com ~/path/to/new/cert.pem ~/path/to/new/priv.key\n</pre>",
   133  	CmdFunc: func(settings *models.Settings) func(cmd *cli.Cmd) {
   134  		return func(subCmd *cli.Cmd) {
   135  			name := subCmd.StringArg("NAME", "", "The name of this SSL certificate and private key pair")
   136  			pubKeyPath := subCmd.StringArg("PUBLIC_KEY_PATH", "", "The path to a public key file in PEM format")
   137  			privKeyPath := subCmd.StringArg("PRIVATE_KEY_PATH", "", "The path to an unencrypted private key file in PEM format")
   138  			downStream := subCmd.StringOpt("down-stream", "service_proxy", "The down-stream service the cert belongs to.")
   139  			selfSigned := subCmd.BoolOpt("s self-signed", false, "Whether or not the given SSL certificate and private key are self signed")
   140  			resolve := subCmd.BoolOpt("r resolve", true, "Whether or not to attempt to automatically resolve incomplete SSL certificate issues")
   141  			subCmd.Action = func() {
   142  				if _, err := auth.New(settings, prompts.New()).Signin(); err != nil {
   143  					logrus.Fatal(err.Error())
   144  				}
   145  				if err := config.CheckRequiredAssociation(settings); err != nil {
   146  					logrus.Fatal(err.Error())
   147  				}
   148  				err := CmdUpdate(*name, *pubKeyPath, *privKeyPath, *downStream, *selfSigned, *resolve, New(settings), services.New(settings), ssl.New(settings))
   149  				if err != nil {
   150  					logrus.Fatal(err.Error())
   151  				}
   152  			}
   153  			subCmd.Spec = "NAME PUBLIC_KEY_PATH PRIVATE_KEY_PATH [-s] [-r] [--down-stream]"
   154  		}
   155  	},
   156  }
   157  
   158  // ICerts
   159  type ICerts interface {
   160  	Create(name, pubKey, privKey, svcID string) error
   161  	CreateLetsEncrypt(name, svcID string) error
   162  	Update(name, pubKey, privKey, svcID string) error
   163  	List(svcID string) (*[]models.Cert, error)
   164  	Rm(name, svcID string) error
   165  }
   166  
   167  // SCerts is a concrete implementation of ICerts
   168  type SCerts struct {
   169  	Settings *models.Settings
   170  }
   171  
   172  // New returns an instance of ICerts
   173  func New(settings *models.Settings) ICerts {
   174  	return &SCerts{
   175  		Settings: settings,
   176  	}
   177  }