storj/cmd/identity/main.go

249 lines
6.4 KiB
Go
Raw Normal View History

2019-01-24 20:15:10 +00:00
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package main
import (
"crypto/x509/pkix"
"encoding/json"
"fmt"
2019-01-26 14:59:53 +00:00
"os"
"path/filepath"
"github.com/spf13/cobra"
"github.com/zeebo/errs"
"storj.io/storj/internal/fpath"
"storj.io/storj/internal/version"
"storj.io/storj/pkg/certificates"
"storj.io/storj/pkg/cfgstruct"
"storj.io/storj/pkg/identity"
"storj.io/storj/pkg/peertls/extensions"
"storj.io/storj/pkg/pkcrypto"
"storj.io/storj/pkg/process"
)
const (
defaultSignerAddress = "certs.alpha.storj.io:8888"
)
var (
rootCmd = &cobra.Command{
Use: "identity",
Short: "Identity management",
}
newServiceCmd = &cobra.Command{
2019-01-24 15:41:16 +00:00
Use: "create <service>",
Short: "Create a new full identity for a service",
Args: cobra.ExactArgs(1),
RunE: cmdNewService,
Annotations: map[string]string{"type": "setup"},
}
2019-01-25 16:55:45 +00:00
authorizeCmd = &cobra.Command{
2019-01-24 15:41:16 +00:00
Use: "authorize <service> <auth-token>",
Short: "Send a certificate signing request for a service's CA certificate",
2019-01-24 15:41:16 +00:00
Args: cobra.ExactArgs(2),
2019-01-25 16:55:45 +00:00
RunE: cmdAuthorize,
Annotations: map[string]string{"type": "setup"},
}
//nolint
config struct {
Difficulty uint64 `default:"30" help:"minimum difficulty for identity generation"`
Concurrency uint `default:"4" help:"number of concurrent workers for certificate authority generation"`
ParentCertPath string `help:"path to the parent authority's certificate chain"`
ParentKeyPath string `help:"path to the parent authority's private key"`
Signer certificates.CertClientConfig
// TODO: ideally the default is the latest version; can't interpolate struct tags
Version uint `default:"0" help:"identity version to use when creating an identity or CA"`
}
2019-02-06 16:40:55 +00:00
identityDir, configDir string
defaultIdentityDir = fpath.ApplicationDir("storj", "identity")
defaultConfigDir = fpath.ApplicationDir("storj", "identity")
)
func init() {
rootCmd.AddCommand(newServiceCmd)
2019-01-25 16:55:45 +00:00
rootCmd.AddCommand(authorizeCmd)
cfgstruct.Bind(newServiceCmd.Flags(), &config, defaults, cfgstruct.ConfDir(defaultConfigDir), cfgstruct.IdentityDir(defaultIdentityDir))
cfgstruct.Bind(authorizeCmd.Flags(), &config, defaults, cfgstruct.ConfDir(defaultConfigDir), cfgstruct.IdentityDir(defaultIdentityDir))
}
func main() {
process.Exec(rootCmd)
}
func serviceDirectory(serviceName string) string {
return filepath.Join(identityDir, serviceName)
}
func cmdNewService(cmd *cobra.Command, args []string) error {
ctx := process.Ctx(cmd)
err := version.CheckProcessVersion(ctx, version.Config{}, version.Build, "Identity")
if err != nil {
return err
}
serviceDir := serviceDirectory(args[0])
caCertPath := filepath.Join(serviceDir, "ca.cert")
caKeyPath := filepath.Join(serviceDir, "ca.key")
identCertPath := filepath.Join(serviceDir, "identity.cert")
identKeyPath := filepath.Join(serviceDir, "identity.key")
caConfig := identity.CASetupConfig{
CertPath: caCertPath,
KeyPath: caKeyPath,
Difficulty: config.Difficulty,
Concurrency: config.Concurrency,
ParentCertPath: config.ParentCertPath,
ParentKeyPath: config.ParentKeyPath,
VersionNumber: config.Version,
}
status, err := caConfig.Status()
if err != nil {
return err
}
if status != identity.NoCertNoKey {
return errs.New("CA certificate and/or key already exists, NOT overwriting!")
}
identConfig := identity.SetupConfig{
CertPath: identCertPath,
KeyPath: identKeyPath,
}
status, err = identConfig.Status()
if err != nil {
return err
}
if status != identity.NoCertNoKey {
return errs.New("Identity certificate and/or key already exists, NOT overwriting!")
}
ca, caerr := caConfig.Create(ctx, os.Stdout)
if caerr != nil {
return caerr
}
_, iderr := identConfig.Create(ca)
if iderr != nil {
return iderr
}
fmt.Printf("Unsigned identity is located in %q\n", serviceDir)
fmt.Println("Please *move* CA key to secure storage - it is only needed for identity management!")
fmt.Printf("\t%s\n", caConfig.KeyPath)
return nil
}
2019-01-25 16:55:45 +00:00
func cmdAuthorize(cmd *cobra.Command, args []string) error {
ctx := process.Ctx(cmd)
err := version.CheckProcessVersion(ctx, version.Config{}, version.Build, "Identity")
if err != nil {
return err
}
serviceDir := serviceDirectory(args[0])
2019-01-24 15:41:16 +00:00
authToken := args[1]
caCertPath := filepath.Join(serviceDir, "ca.cert")
2019-02-06 16:40:55 +00:00
caConfig := identity.PeerCAConfig{
CertPath: caCertPath,
}
identCertPath := filepath.Join(serviceDir, "identity.cert")
identKeyPath := filepath.Join(serviceDir, "identity.key")
identConfig := identity.Config{
CertPath: identCertPath,
KeyPath: identKeyPath,
}
ca, err := caConfig.Load()
if err != nil {
return err
}
ident, err := identConfig.Load()
if err != nil {
return err
}
if config.Signer.Address == "" {
config.Signer.Address = defaultSignerAddress
}
2019-01-24 15:41:16 +00:00
signedChainBytes, err := config.Signer.Sign(ctx, ident, authToken)
if err != nil {
return errs.New("error occurred while signing certificate: %s\n(identity files were still generated and saved, if you try again existing files will be loaded)", err)
}
signedChain, err := pkcrypto.CertsFromDER(signedChainBytes)
if err != nil {
return nil
}
err = caConfig.SaveBackup(ca)
if err != nil {
return err
}
2019-04-08 19:15:19 +01:00
// NB: signedChain is this identity's CA + signer chain.
ca.Cert = signedChain[0]
ca.RestChain = signedChain[1:]
2019-02-06 16:40:55 +00:00
err = caConfig.Save(ca)
if err != nil {
return err
}
2019-02-06 16:40:55 +00:00
err = identConfig.PeerConfig().SaveBackup(ident.PeerIdentity())
if err != nil {
return err
}
ident.RestChain = signedChain[1:]
ident.CA = ca.Cert
2019-02-06 16:40:55 +00:00
err = identConfig.PeerConfig().Save(ident.PeerIdentity())
if err != nil {
return err
}
2019-01-24 15:41:16 +00:00
2019-01-25 16:55:45 +00:00
fmt.Println("Identity successfully authorized using single use authorization token.")
fmt.Printf("Please back-up \"%s\" to a safe location.\n", serviceDir)
return nil
}
func printExtensions(cert []byte, exts []pkix.Extension) error {
hash := pkcrypto.SHA256Hash(cert)
b64Hash, err := json.Marshal(hash)
if err != nil {
return err
}
fmt.Printf("Cert hash: %s\n", b64Hash)
fmt.Println("Extensions:")
for _, e := range exts {
var data interface{}
if e.Id.Equal(extensions.RevocationExtID) {
var rev extensions.Revocation
if err := rev.Unmarshal(e.Value); err != nil {
return err
}
data = rev
} else {
data = e.Value
}
out, err := json.MarshalIndent(data, "", " ")
if err != nil {
return err
}
fmt.Printf("\t%s: %s\n", e.Id, out)
}
return nil
}