2019-01-24 20:15:10 +00:00
// Copyright (C) 2019 Storj Labs, Inc.
2018-08-27 23:23:48 +01:00
// See LICENSE for copying information.
package main
import (
2018-12-18 11:55:55 +00:00
"crypto/x509/pkix"
"encoding/json"
"fmt"
2019-01-26 14:59:53 +00:00
"os"
2019-01-18 10:36:58 +00:00
"path/filepath"
2018-12-18 11:55:55 +00:00
2019-01-25 16:55:45 +00:00
"github.com/fatih/color"
2018-08-27 23:23:48 +01:00
"github.com/spf13/cobra"
2019-01-18 10:36:58 +00:00
"github.com/zeebo/errs"
2018-08-27 23:23:48 +01:00
2018-12-18 11:55:55 +00:00
"storj.io/storj/internal/fpath"
2019-01-18 10:36:58 +00:00
"storj.io/storj/pkg/certificates"
"storj.io/storj/pkg/cfgstruct"
"storj.io/storj/pkg/identity"
2018-12-18 11:55:55 +00:00
"storj.io/storj/pkg/peertls"
2018-08-27 23:23:48 +01:00
"storj.io/storj/pkg/process"
)
2019-01-24 17:23:45 +00:00
const (
defaultSignerAddress = "certs.alpha.storj.io:8888"
)
2018-08-27 23:23:48 +01:00
var (
rootCmd = & cobra . Command {
Use : "identity" ,
Short : "Identity management" ,
}
2019-01-18 10:36:58 +00:00
newServiceCmd = & cobra . Command {
2019-01-24 15:41:16 +00:00
Use : "create <service>" ,
2019-01-18 10:36:58 +00:00
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>" ,
2019-01-18 10:36:58 +00:00
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 ,
2019-01-18 10:36:58 +00:00
Annotations : map [ string ] string { "type" : "setup" } ,
}
//nolint
config struct {
2019-01-23 11:36:19 +00:00
Difficulty uint64 ` default:"30" help:"minimum difficulty for identity generation" `
2019-01-18 10:36:58 +00:00
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
}
2019-01-22 12:35:48 +00:00
identityDir string
defaultIdentityDir = fpath . ApplicationDir ( "storj" , "identity" )
2018-08-27 23:23:48 +01:00
)
2019-01-18 10:36:58 +00:00
func init ( ) {
2019-01-25 16:55:45 +00:00
identityDirParam := cfgstruct . FindIdentityDirParam ( )
if identityDirParam != "" {
defaultIdentityDir = identityDirParam
}
2019-01-22 12:35:48 +00:00
rootCmd . PersistentFlags ( ) . StringVar ( & identityDir , "identity-dir" , defaultIdentityDir , "root directory for identity output" )
2019-01-18 10:36:58 +00:00
rootCmd . AddCommand ( newServiceCmd )
2019-01-25 16:55:45 +00:00
rootCmd . AddCommand ( authorizeCmd )
2019-01-18 10:36:58 +00:00
2019-01-22 14:34:40 +00:00
cfgstruct . Bind ( newServiceCmd . Flags ( ) , & config , cfgstruct . IdentityDir ( defaultIdentityDir ) )
2019-01-25 16:55:45 +00:00
cfgstruct . Bind ( authorizeCmd . Flags ( ) , & config , cfgstruct . IdentityDir ( defaultIdentityDir ) )
2019-01-18 10:36:58 +00:00
}
2018-08-27 23:23:48 +01:00
func main ( ) {
process . Exec ( rootCmd )
}
2018-12-18 11:55:55 +00:00
2019-01-18 10:36:58 +00:00
func serviceDirectory ( serviceName string ) string {
2019-01-22 12:35:48 +00:00
return filepath . Join ( identityDir , serviceName )
2019-01-18 10:36:58 +00:00
}
func cmdNewService ( cmd * cobra . Command , args [ ] string ) error {
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 ,
}
if caConfig . Status ( ) != identity . NoCertNoKey {
return errs . New ( "CA certificate and/or key already exits, NOT overwriting!" )
}
identConfig := identity . SetupConfig {
CertPath : identCertPath ,
KeyPath : identKeyPath ,
}
if identConfig . Status ( ) != identity . NoCertNoKey {
return errs . New ( "Identity certificate and/or key already exits, NOT overwriting!" )
}
2019-02-01 03:57:14 +00:00
ca , caerr := caConfig . Create ( process . Ctx ( cmd ) , os . Stdout )
if caerr != nil {
return caerr
}
2019-01-18 10:36:58 +00:00
_ , iderr := identConfig . Create ( ca )
2019-01-24 17:23:45 +00:00
if iderr != nil {
return iderr
}
2019-01-18 10:36:58 +00:00
2019-01-24 17:23:45 +00:00
fmt . Printf ( "Unsigned identity is located in %q\n" , serviceDir )
2019-01-25 16:55:45 +00:00
fmt . Println ( color . CyanString ( "Please *move* CA key to secure storage - it is only needed for identity management!" ) )
fmt . Println ( color . CyanString ( "\t%s" , caConfig . KeyPath ) )
2019-01-24 17:23:45 +00:00
return nil
2019-01-18 10:36:58 +00:00
}
2019-01-25 16:55:45 +00:00
func cmdAuthorize ( cmd * cobra . Command , args [ ] string ) error {
2019-01-18 10:36:58 +00:00
ctx := process . Ctx ( cmd )
serviceDir := serviceDirectory ( args [ 0 ] )
2019-01-24 15:41:16 +00:00
authToken := args [ 1 ]
2019-01-18 10:36:58 +00:00
caCertPath := filepath . Join ( serviceDir , "ca.cert" )
caKeyPath := filepath . Join ( serviceDir , "ca.key" )
caConfig := identity . FullCAConfig {
CertPath : caCertPath ,
KeyPath : caKeyPath ,
}
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
}
2019-01-24 17:23:45 +00:00
if config . Signer . Address == "" {
config . Signer . Address = defaultSignerAddress
}
2019-01-24 15:41:16 +00:00
signedChainBytes , err := config . Signer . Sign ( ctx , ident , authToken )
2019-01-18 10:36:58 +00:00
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 := identity . ParseCertChain ( signedChainBytes )
if err != nil {
return nil
}
err = caConfig . SaveBackup ( ca )
if err != nil {
return err
}
ca . Cert = signedChain [ 0 ]
ca . RestChain = signedChain [ 1 : ]
err = identity . FullCAConfig {
CertPath : caConfig . CertPath ,
} . Save ( ca )
if err != nil {
return err
}
err = identConfig . SaveBackup ( ident )
if err != nil {
return err
}
ident . RestChain = signedChain [ 1 : ]
ident . CA = ca . Cert
err = identity . Config {
CertPath : identConfig . CertPath ,
} . Save ( ident )
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 )
2019-01-18 10:36:58 +00:00
return nil
}
2018-12-18 11:55:55 +00:00
func printExtensions ( cert [ ] byte , exts [ ] pkix . Extension ) error {
hash , err := peertls . SHA256Hash ( cert )
if err != nil {
return err
}
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 { }
switch e . Id . String ( ) {
case peertls . ExtensionIDs [ peertls . RevocationExtID ] . String ( ) :
var rev peertls . Revocation
if err := rev . Unmarshal ( e . Value ) ; err != nil {
return err
}
data = rev
default :
data = e . Value
}
out , err := json . MarshalIndent ( data , "" , " " )
if err != nil {
return err
}
fmt . Printf ( "\t%s: %s\n" , e . Id , out )
}
return nil
}