github.com/wostzone/hub/auth@v0.0.0-20220118060317-7bb375743b17/cmd/auth/main.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "crypto/ecdsa" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "path" 10 "strings" 11 "time" 12 13 "github.com/docopt/docopt-go" 14 "github.com/sirupsen/logrus" 15 "github.com/wostzone/hub/auth/pkg/aclstore" 16 "github.com/wostzone/hub/auth/pkg/authenticate" 17 "github.com/wostzone/hub/auth/pkg/authorize" 18 "github.com/wostzone/hub/auth/pkg/unpwstore" 19 "github.com/wostzone/hub/lib/client/pkg/certs" 20 "github.com/wostzone/hub/lib/client/pkg/config" 21 "github.com/wostzone/hub/lib/client/pkg/signing" 22 "github.com/wostzone/hub/lib/serve/pkg/certsetup" 23 "github.com/wostzone/hub/lib/serve/pkg/hubnet" 24 ) 25 26 //// Commandline commands 27 //const ( 28 // CmdCertBundle = "certbundle" 29 // CmdClientcert = "clientcert" 30 // CmdSetPasswd = "setpasswd" 31 // CmdSetRole = "setrole" 32 //) 33 const Version = `0.3-alpha` 34 35 func main() { 36 binFolder := path.Dir(os.Args[0]) 37 homeFolder := path.Dir(binFolder) 38 ParseArgs(homeFolder, os.Args[1:]) 39 } 40 41 // ParseArgs to handle commandline arguments 42 func ParseArgs(homeFolder string, args []string) { 43 // var err error 44 configFolder := path.Join(homeFolder, "config") 45 certsFolder := path.Join(homeFolder, "certs") 46 // configFolder := path.Join(homeFolder, "config") 47 // ouRole := certsetup.OUClient 48 // genKeys := false 49 ifName, mac, ip := hubnet.GetOutboundInterface("") 50 _ = ifName 51 _ = mac 52 sanName := ip.String() 53 var optConf struct { 54 // commands 55 Certbundle bool 56 Clientcert bool 57 Devicecert bool 58 Setpassword bool 59 Setrole bool 60 // arguments 61 Loginid string 62 Deviceid string 63 Groupid string 64 Role string 65 // options 66 Aclfile string 67 Config string 68 Certs string 69 Hostname string 70 Output string 71 Pubkey string 72 Iter int 73 Verbose bool 74 } 75 usage := ` 76 Usage: 77 auth certbundle [-v --certs=CertFolder] [--hostname=hostname] 78 auth clientcert [-v --certs=CertFolder --pubkey=pubkeyfile] <loginID> 79 auth devicecert [-v --certs=CertFolder --pubkey=pubkeyfile] <deviceID> 80 auth setpassword [-v -c configFolder] [-i iterations] <loginID> 81 auth setrole [-v -c configFolder --aclfile=aclfile] <loginID> <groupID> <role> 82 auth --help | --version 83 84 Commands: 85 certbundle Generate or refresh the Hub certificate bundle, optionally provide a subject hostname or ip 86 clientcert Generate a signed client certificate, with pub/private keys if not given 87 devicecert Generate a signed device certificate, with pub/private keys if not given 88 setpassword Set user password 89 setrole Set user role in group 90 91 Arguments: 92 loginID used as the certificate CN, login name and certificate filename (loginID-cert.pem) 93 groupID group for access control 94 role one of viewer, editor, manager, thing, or none to delete 95 96 Options: 97 --aclfile=AclFile use a different acl file instead of the default config/` + aclstore.DefaultAclFile + ` 98 -e --certs=CertFolder location of Hub certificates [default: ` + certsFolder + `] 99 -c --config=ConfigFolder location of Hub config folder [default: ` + configFolder + `] 100 -o --hostname=Hostname name or IP address to use on the certificate 101 -p --pubkey=PubKeyfile use this public key file to generate certificate, instead of a new key pair 102 -i --iter=iterations Number of iterations for generating password [default: 10] 103 -h --help show this help 104 -v --verbose show info logging 105 --version show app version 106 ` 107 opts, err := docopt.ParseArgs(usage, args, Version) 108 if err != nil { 109 fmt.Printf("Parse Error: %s\n", err) 110 os.Exit(1) 111 } 112 113 err = opts.Bind(&optConf) 114 115 if optConf.Verbose { 116 logrus.SetLevel(logrus.InfoLevel) 117 } else { 118 logrus.SetLevel(logrus.WarnLevel) 119 } 120 121 if err != nil { 122 fmt.Printf("Bind Error: %s\n", err) 123 os.Exit(1) 124 } 125 _ = opts 126 if optConf.Certbundle { 127 if optConf.Hostname != "" { 128 sanName = optConf.Hostname 129 } 130 fmt.Printf("Generating certificate Bundle. Certfolder=%s. names=%s\n", optConf.Certs, sanName) 131 err = HandleCreateCertbundle(optConf.Certs, sanName) 132 } else if optConf.Clientcert { 133 fmt.Printf("Generating Client certificate using CA from %s\n", optConf.Certs) 134 err = HandleCreateClientCert(optConf.Certs, optConf.Loginid, optConf.Pubkey) 135 } else if optConf.Devicecert { 136 fmt.Printf("Generating Thing device certificate using CA from %s\n", optConf.Certs) 137 err = HandleCreateDeviceCert(optConf.Certs, optConf.Deviceid, optConf.Pubkey) 138 } else if optConf.Setpassword { 139 fmt.Printf("Set user password\n") 140 err = HandleSetPasswd(optConf.Config, optConf.Loginid, optConf.Iter) 141 } else if optConf.Setrole { 142 fmt.Printf("Set user role in group\n") 143 err = HandleSetRole(optConf.Config, optConf.Loginid, optConf.Groupid, optConf.Role, optConf.Aclfile) 144 } else { 145 err = fmt.Errorf("invalid command") 146 } 147 if err != nil { 148 fmt.Printf("Error: %s\n", err) 149 os.Exit(1) 150 } 151 } 152 153 // CreateKeyPair generate a key pair in PEM format and save it to the cert folder 154 // as <clientID>-pub.pem and <clientID>-key.pem 155 // Returns the public key PEM content 156 func CreateKeyPair(clientID string, certFolder string) (privKey *ecdsa.PrivateKey, err error) { 157 privKey = signing.CreateECDSAKeys() 158 privKeyFile := path.Join(certFolder, clientID+"-priv.pem") 159 pubKeyFile := path.Join(certFolder, clientID+"-pub.pem") 160 err = certs.SaveKeysToPEM(privKey, privKeyFile) 161 if err == nil { 162 pubKeyPem, _ := certs.PublicKeyToPEM(&privKey.PublicKey) 163 err = ioutil.WriteFile(pubKeyFile, []byte(pubKeyPem), 0644) 164 } 165 if err != nil { 166 fmt.Printf("Failed saving keys: %s\n", err) 167 } 168 if err == nil { 169 fmt.Printf("Generated public and private key pair as: %s and %s\n", pubKeyFile, privKeyFile) 170 } 171 return privKey, err 172 } 173 174 // HandleSetPasswd sets the login name and password for a consumer 175 func HandleSetPasswd(configFolder string, username string, iterations int) error { 176 var pwHash string 177 var err error 178 var passwd string 179 reader := bufio.NewReader(os.Stdin) 180 unpwFilePath := path.Join(configFolder, unpwstore.DefaultPasswordFile) 181 unpwStore := unpwstore.NewPasswordFileStore(unpwFilePath, "auth.main.HandleSetPasswd") 182 err = unpwStore.Open() 183 if err == nil { 184 fmt.Printf("\nNew Password: ") 185 passwd, err = reader.ReadString('\n') 186 passwd = strings.Replace(passwd, "\n", "", -1) 187 if err != nil { 188 return err 189 } 190 if passwd == "" { 191 return fmt.Errorf("missing password") 192 } 193 // pwHash, err = authen.CreatePasswordHash(passwd, authen.PWHASH_ARGON2id, uint(iterations)) 194 pwHash, err = authenticate.CreatePasswordHash(passwd, authenticate.PWHASH_ARGON2id, uint(iterations)) 195 } 196 if err == nil { 197 err = unpwStore.SetPasswordHash(username, pwHash) 198 } 199 if err == nil { 200 unpwStore.Close() 201 fmt.Printf("Password updated for user %s\n", username) 202 } 203 return err 204 } 205 206 // HandleCreateCertbundle generates the hub certificate bundle CA, Hub and Plugin keys 207 // and certificates. 208 // If the CA certificate already exist it is NOT updated 209 // If the Hub and Plugin certificates already exist, they are renewed 210 func HandleCreateCertbundle(certsFolder string, sanName string) error { 211 err := certsetup.CreateCertificateBundle([]string{sanName}, certsFolder) 212 if err != nil { 213 return err 214 } 215 fmt.Printf("Server and Plugin certificates generated in %s\n", certsFolder) 216 return nil 217 } 218 219 // HandleCreateClientCert creates a consumer client certificate and optionally private/public keypair 220 // certFolder where to find the CA certificate and key used to sign the client certificate 221 // clientID for the CN of the client certificate. Used to identify the consumer. 222 // pubKeyFile with path to the client's public key of the certificate 223 func HandleCreateClientCert(certFolder string, clientID string, pubKeyFile string) error { 224 var pubKey *ecdsa.PublicKey 225 ou := certsetup.OUClient 226 pemPath := path.Join(certFolder, config.DefaultCaCertFile) 227 caCert, err := certs.LoadX509CertFromPEM(pemPath) 228 if err != nil { 229 return err 230 } 231 pemPath = path.Join(certFolder, config.DefaultCaKeyFile) 232 caKey, err := certs.LoadKeysFromPEM(pemPath) 233 if err != nil { 234 return err 235 } 236 // If a public key file is given, use it, otherwise generate a pair 237 if pubKeyFile != "" { 238 fmt.Printf("Using public key file: %s\n", pubKeyFile) 239 pubKey, err = certs.LoadPublicKeyFromPEM(pubKeyFile) 240 if err != nil { 241 return err 242 } 243 } else { 244 fmt.Printf("No public key file was provided. Creating a key pair ") // no newline 245 privKey, err := CreateKeyPair(clientID, "") 246 pubKey = &privKey.PublicKey 247 if err != nil { 248 return err 249 } 250 } 251 durationDays := certsetup.DefaultCertDurationDays 252 cert, err := certsetup.CreateHubClientCert( 253 clientID, ou, pubKey, caCert, caKey, time.Now(), durationDays) 254 if err != nil { 255 return err 256 } 257 pemPath = path.Join(".", clientID+"-cert.pem") 258 certs.SaveX509CertToPEM(cert, pemPath) 259 260 fmt.Printf("Client certificate saved at %s\n", pemPath) 261 return nil 262 } 263 264 // HandleCreateDeviceCert creates a device client certificate for a device and save it in the certFolder 265 // This is similar to creating a consumer certificate 266 func HandleCreateDeviceCert(certFolder string, deviceID string, pubKeyFile string) error { 267 const deviceCertValidityDays = 30 268 var pubKey *ecdsa.PublicKey 269 pemPath := path.Join(certFolder, config.DefaultCaCertFile) 270 caCert, err := certs.LoadX509CertFromPEM(pemPath) 271 if err != nil { 272 return err 273 } 274 pemPath = path.Join(certFolder, config.DefaultCaKeyFile) 275 caKey, err := certs.LoadKeysFromPEM(pemPath) 276 if err != nil { 277 return err 278 } 279 // If a public key file is given, use it, otherwise generate a pair 280 if pubKeyFile != "" { 281 fmt.Printf("Using public key file: %s\n", pubKeyFile) 282 pubKey, err = certs.LoadPublicKeyFromPEM(pubKeyFile) 283 if err != nil { 284 return err 285 } 286 } else { 287 fmt.Printf("No public key file was provided. Creating a new key pair ") // no newline 288 privKey, err := CreateKeyPair(deviceID, "") 289 pubKey = &privKey.PublicKey 290 if err != nil { 291 return err 292 } 293 } 294 295 certPEM, err := certsetup.CreateHubClientCert( 296 deviceID, certsetup.OUIoTDevice, pubKey, 297 caCert, caKey, 298 time.Now(), deviceCertValidityDays) 299 if err != nil { 300 return err 301 } 302 // save the new certificate 303 pemPath = path.Join(".", deviceID+"-cert.pem") 304 err = certs.SaveX509CertToPEM(certPEM, pemPath) 305 306 fmt.Printf("Device certificate saved at %s\n", pemPath) 307 return err 308 } 309 310 // HandleSetRole sets the role of a client in a group. 311 func HandleSetRole(configFolder string, clientID string, groupID string, role string, aclFile string) error { 312 if role != authorize.GroupRoleEditor && role != authorize.GroupRoleViewer && 313 role != authorize.GroupRoleManager && role != authorize.GroupRoleThing && role != authorize.GroupRoleNone { 314 err := fmt.Errorf("invalid role '%s'", role) 315 return err 316 } 317 aclFilePath := path.Join(configFolder, aclstore.DefaultAclFile) 318 if aclFile != "" { 319 // option to specify an acl file wrt home 320 aclFilePath = path.Join(path.Dir(configFolder), aclFile) 321 } 322 aclStore := aclstore.NewAclFileStore(aclFilePath, "author.main.HandleSetRole") 323 err := aclStore.Open() 324 if err == nil { 325 err = aclStore.SetRole(clientID, groupID, role) 326 } 327 if err == nil { 328 fmt.Printf("Client '%s' role set to '%s' for group '%s'\n", clientID, role, groupID) 329 } 330 aclStore.Close() 331 return err 332 }