2015-11-19 05:45:05 +00:00
|
|
|
package rabbithole
|
|
|
|
|
|
|
|
import (
|
2019-04-12 15:51:37 +00:00
|
|
|
"crypto/sha256"
|
|
|
|
"crypto/sha512"
|
|
|
|
"encoding/base64"
|
2015-11-19 05:45:05 +00:00
|
|
|
"encoding/json"
|
2019-04-12 15:51:37 +00:00
|
|
|
"math/rand"
|
2015-11-19 05:45:05 +00:00
|
|
|
"net/http"
|
2019-11-06 13:15:06 +00:00
|
|
|
"net/url"
|
2015-11-19 05:45:05 +00:00
|
|
|
)
|
|
|
|
|
2019-11-06 13:15:06 +00:00
|
|
|
// HashingAlgorithm represents a hashing algorithm used
|
|
|
|
// by RabbitMQ's an internal authentication backend.
|
2019-04-12 15:51:37 +00:00
|
|
|
type HashingAlgorithm string
|
|
|
|
|
|
|
|
func (algo HashingAlgorithm) String() string {
|
|
|
|
return string(algo)
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
2019-11-06 13:15:06 +00:00
|
|
|
// HashingAlgorithmSHA256 sets password hashing algorithm to SHA-256.
|
2019-04-12 15:51:37 +00:00
|
|
|
HashingAlgorithmSHA256 HashingAlgorithm = "rabbit_password_hashing_sha256"
|
2019-11-06 13:15:06 +00:00
|
|
|
// HashingAlgorithmSHA512 sets password hashing algorithm to SHA-512.
|
2019-04-12 15:51:37 +00:00
|
|
|
HashingAlgorithmSHA512 HashingAlgorithm = "rabbit_password_hashing_sha512"
|
|
|
|
|
2019-11-06 13:15:06 +00:00
|
|
|
// HashingAlgorithmMD5 provided to support responses that include users created
|
|
|
|
// before RabbitMQ 3.6 and other legacy scenarios. This algorithm is
|
|
|
|
// deprecated.
|
2019-04-12 15:51:37 +00:00
|
|
|
HashingAlgorithmMD5 HashingAlgorithm = "rabbit_password_hashing_md5"
|
|
|
|
)
|
|
|
|
|
2019-11-06 13:15:06 +00:00
|
|
|
// UserInfo represents a user record. Only relevant when internal authentication
|
|
|
|
// backend is used.
|
2015-11-19 05:45:05 +00:00
|
|
|
type UserInfo struct {
|
2019-04-12 15:51:37 +00:00
|
|
|
Name string `json:"name"`
|
|
|
|
PasswordHash string `json:"password_hash"`
|
|
|
|
HashingAlgorithm HashingAlgorithm `json:"hashing_algorithm,omitempty"`
|
2015-11-19 05:45:05 +00:00
|
|
|
// Tags control permissions. Built-in tags: administrator, management, policymaker.
|
|
|
|
Tags string `json:"tags"`
|
|
|
|
}
|
|
|
|
|
2019-11-06 13:15:06 +00:00
|
|
|
// UserSettings represents properties of a user. Used to create users.
|
|
|
|
// Tags must be comma-separated.
|
2015-11-19 05:45:05 +00:00
|
|
|
type UserSettings struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
// Tags control permissions. Administrator grants full
|
|
|
|
// permissions, management grants management UI and HTTP API
|
|
|
|
// access, policymaker grants policy management permissions.
|
|
|
|
Tags string `json:"tags"`
|
|
|
|
|
|
|
|
// *never* returned by RabbitMQ. Set by the client
|
|
|
|
// to create/update a user. MK.
|
2019-04-12 15:51:37 +00:00
|
|
|
Password string `json:"password,omitempty"`
|
|
|
|
PasswordHash string `json:"password_hash,omitempty"`
|
|
|
|
HashingAlgorithm HashingAlgorithm `json:"hashing_algorithm,omitempty"`
|
2015-11-19 05:45:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// GET /api/users
|
|
|
|
//
|
|
|
|
|
|
|
|
// Example response:
|
|
|
|
// [{"name":"guest","password_hash":"8LYTIFbVUwi8HuV/dGlp2BYsD1I=","tags":"administrator"}]
|
|
|
|
|
2019-11-06 13:15:06 +00:00
|
|
|
// ListUsers returns a list of all users in a cluster.
|
2015-11-19 05:45:05 +00:00
|
|
|
func (c *Client) ListUsers() (rec []UserInfo, err error) {
|
|
|
|
req, err := newGETRequest(c, "users/")
|
|
|
|
if err != nil {
|
|
|
|
return []UserInfo{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = executeAndParseRequest(c, req, &rec); err != nil {
|
|
|
|
return []UserInfo{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return rec, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// GET /api/users/{name}
|
|
|
|
//
|
|
|
|
|
2019-11-06 13:15:06 +00:00
|
|
|
// GetUser returns information about individual user.
|
2015-11-19 05:45:05 +00:00
|
|
|
func (c *Client) GetUser(username string) (rec *UserInfo, err error) {
|
2019-11-06 13:15:06 +00:00
|
|
|
req, err := newGETRequest(c, "users/"+url.PathEscape(username))
|
2015-11-19 05:45:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = executeAndParseRequest(c, req, &rec); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return rec, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// PUT /api/users/{name}
|
|
|
|
//
|
|
|
|
|
2019-11-06 13:15:06 +00:00
|
|
|
// PutUser updates information about an individual user.
|
2015-11-19 05:45:05 +00:00
|
|
|
func (c *Client) PutUser(username string, info UserSettings) (res *http.Response, err error) {
|
|
|
|
body, err := json.Marshal(info)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-11-06 13:15:06 +00:00
|
|
|
req, err := newRequestWithBody(c, "PUT", "users/"+url.PathEscape(username), body)
|
2015-11-19 05:45:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-11-06 13:15:06 +00:00
|
|
|
if res, err = executeRequest(c, req); err != nil {
|
2015-11-19 05:45:05 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
2019-11-06 13:15:06 +00:00
|
|
|
// PutUserWithoutPassword creates a passwordless user. Such users can only authenticate
|
|
|
|
// using an X.509 certificate or another authentication mechanism (or backend) that does not
|
|
|
|
// use passwords..
|
2017-07-18 14:15:54 +00:00
|
|
|
func (c *Client) PutUserWithoutPassword(username string, info UserSettings) (res *http.Response, err error) {
|
|
|
|
body, err := json.Marshal(UserInfo{Tags: info.Tags})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-11-06 13:15:06 +00:00
|
|
|
req, err := newRequestWithBody(c, "PUT", "users/"+url.PathEscape(username), body)
|
2017-07-18 14:15:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-11-06 13:15:06 +00:00
|
|
|
if res, err = executeRequest(c, req); err != nil {
|
2017-07-18 14:15:54 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
2015-11-19 05:45:05 +00:00
|
|
|
//
|
|
|
|
// DELETE /api/users/{name}
|
|
|
|
//
|
|
|
|
|
2019-11-06 13:15:06 +00:00
|
|
|
// DeleteUser deletes a user by name.
|
2015-11-19 05:45:05 +00:00
|
|
|
func (c *Client) DeleteUser(username string) (res *http.Response, err error) {
|
2019-11-06 13:15:06 +00:00
|
|
|
req, err := newRequestWithBody(c, "DELETE", "users/"+url.PathEscape(username), nil)
|
2015-11-19 05:45:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-11-06 13:15:06 +00:00
|
|
|
if res, err = executeRequest(c, req); err != nil {
|
2015-11-19 05:45:05 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return res, nil
|
|
|
|
}
|
2019-04-12 15:51:37 +00:00
|
|
|
|
|
|
|
//
|
|
|
|
// Password Hash generation
|
|
|
|
//
|
|
|
|
|
|
|
|
const characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
|
|
|
2019-11-06 13:15:06 +00:00
|
|
|
// GenerateSalt generates a password salt. Used to compute password hashes
|
|
|
|
// when creating or updating user information.
|
|
|
|
// See https://www.rabbitmq.com/passwords.html#computing-password-hash
|
|
|
|
// for details.
|
2019-04-12 15:51:37 +00:00
|
|
|
func GenerateSalt(n int) string {
|
|
|
|
bs := make([]byte, n)
|
|
|
|
for i := range bs {
|
|
|
|
bs[i] = characters[rand.Intn(len(characters))]
|
|
|
|
}
|
|
|
|
return string(bs)
|
|
|
|
}
|
|
|
|
|
2019-11-06 13:15:06 +00:00
|
|
|
// SaltedPasswordHashSHA256 is used to compute SHA-256 password hashes
|
|
|
|
// when creating or updating user information.
|
|
|
|
// See https://www.rabbitmq.com/passwords.html#computing-password-hash
|
|
|
|
// for details.
|
2019-04-12 15:51:37 +00:00
|
|
|
func SaltedPasswordHashSHA256(password string) (string, string) {
|
|
|
|
salt := GenerateSalt(4)
|
|
|
|
hashed := sha256.Sum256([]byte(salt + password))
|
|
|
|
return salt, string(hashed[:])
|
|
|
|
}
|
|
|
|
|
2019-11-06 13:15:06 +00:00
|
|
|
// Base64EncodedSaltedPasswordHashSHA256 produces a salted hash value expected by the HTTP API.
|
2019-04-12 15:51:37 +00:00
|
|
|
// See https://www.rabbitmq.com/passwords.html#computing-password-hash
|
|
|
|
// for details.
|
|
|
|
func Base64EncodedSaltedPasswordHashSHA256(password string) string {
|
|
|
|
salt, saltedHash := SaltedPasswordHashSHA256(password)
|
|
|
|
return base64.URLEncoding.EncodeToString([]byte(salt + saltedHash))
|
|
|
|
}
|
|
|
|
|
2019-11-06 13:15:06 +00:00
|
|
|
// SaltedPasswordHashSHA512 is used to compute SHA-512 password hashes
|
|
|
|
// when creating or updating user information.
|
|
|
|
// See https://www.rabbitmq.com/passwords.html#computing-password-hash
|
|
|
|
// for details.
|
2019-04-12 15:51:37 +00:00
|
|
|
func SaltedPasswordHashSHA512(password string) (string, string) {
|
|
|
|
salt := GenerateSalt(4)
|
|
|
|
hashed := sha512.Sum512([]byte(salt + password))
|
|
|
|
return salt, string(hashed[:])
|
|
|
|
}
|
|
|
|
|
2019-11-06 13:15:06 +00:00
|
|
|
// Base64EncodedSaltedPasswordHashSHA512 produces a salted hash value expected by the HTTP API.
|
|
|
|
// See https://www.rabbitmq.com/passwords.html#computing-password-hash
|
|
|
|
// for details.
|
2019-04-12 15:51:37 +00:00
|
|
|
func Base64EncodedSaltedPasswordHashSHA512(password string) string {
|
|
|
|
salt, saltedHash := SaltedPasswordHashSHA512(password)
|
|
|
|
return base64.URLEncoding.EncodeToString([]byte(salt + saltedHash))
|
|
|
|
}
|