-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #14 from PelicanPlatform/namespace-registry
Add Namespace Management CLI Commands
- Loading branch information
Showing
5 changed files
with
421 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package main | ||
|
||
import ( | ||
"os" | ||
|
||
"github.com/spf13/cobra" | ||
log "github.com/sirupsen/logrus" | ||
) | ||
|
||
// These functions are just placeholders. You need to provide actual implementation. | ||
|
||
var withIdentity bool | ||
var prefix string | ||
var host string | ||
var jwks bool | ||
var pubkeyPath string | ||
var privkeyPath string | ||
|
||
func registerANamespace(cmd *cobra.Command, args []string) { | ||
endpoint := host + "/registry" | ||
if prefix == "" { | ||
log.Error("Error: prefix is required") | ||
os.Exit(1) | ||
} | ||
|
||
if withIdentity { | ||
err := namespace_register_with_identity(pubkeyPath, privkeyPath, endpoint, prefix) | ||
if err != nil { | ||
log.Error(err) | ||
os.Exit(1) | ||
} | ||
} else { | ||
err := namespace_register(pubkeyPath, privkeyPath, endpoint, "", prefix) | ||
if err != nil { | ||
log.Error(err) | ||
os.Exit(1) | ||
} | ||
} | ||
} | ||
|
||
func deleteANamespace(cmd *cobra.Command, args []string) { | ||
endpoint := host + "/" + prefix | ||
err := delete_namespace(endpoint) | ||
if err != nil { | ||
log.Error(err) | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
func listAllNamespaces(cmd *cobra.Command, args []string) { | ||
endpoint := host | ||
err := list_namespaces(endpoint) | ||
if err != nil { | ||
log.Error(err) | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
func getNamespace(cmd *cobra.Command, args []string) { | ||
if jwks { | ||
endpoint := host + "/" + prefix + "/issuer.jwks" | ||
err := get_namespace(endpoint) | ||
if err != nil { | ||
log.Error(err) | ||
os.Exit(1) | ||
} | ||
} else { | ||
log.Error("Error: get command requires --jwks flag") | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
var namespaceCmd = &cobra.Command{ | ||
Use: "namespace", | ||
Short: "Work with namespaces", | ||
} | ||
|
||
var registerCmd = &cobra.Command{ | ||
Use: "register", | ||
Short: "Register a new namespace", | ||
Run: registerANamespace, | ||
} | ||
|
||
var deleteCmd = &cobra.Command{ | ||
Use: "delete", | ||
Short: "Delete a namespace", | ||
Run: deleteANamespace, | ||
} | ||
|
||
var listCmd = &cobra.Command{ | ||
Use: "list", | ||
Short: "List all namespaces", | ||
Run: listAllNamespaces, | ||
} | ||
|
||
var getCmd = &cobra.Command{ | ||
Use: "get", | ||
Short: "Get a specific namespace", | ||
Run: getNamespace, | ||
} | ||
|
||
func init() { | ||
registerCmd.Flags().StringVar(&prefix, "prefix", "", "prefix for registering namespace") | ||
registerCmd.Flags().BoolVar(&withIdentity, "with-identity", false, "Register a namespace with an identity") | ||
getCmd.Flags().StringVar(&prefix, "prefix", "", "prefix for get namespace") | ||
getCmd.Flags().BoolVar(&jwks, "jwks", false, "Get the jwks of the namespace") | ||
deleteCmd.Flags().StringVar(&prefix, "prefix", "", "prefix for delete namespace") | ||
|
||
namespaceCmd.PersistentFlags().StringVar(&host, "host", "http://localhost:8443/cli-namespaces", "Host of the namespace registry") | ||
namespaceCmd.PersistentFlags().StringVar(&pubkeyPath, "pubkey", "/usr/src/app/pelican/cmd/cert/.well-known/client.jwks", "Path to the public key") | ||
namespaceCmd.PersistentFlags().StringVar(&privkeyPath, "privkey", "/usr/src/app/pelican/cmd/cert/client.key", "Path to the private key") | ||
namespaceCmd.AddCommand(registerCmd) | ||
namespaceCmd.AddCommand(deleteCmd) | ||
namespaceCmd.AddCommand(listCmd) | ||
namespaceCmd.AddCommand(getCmd) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
package main | ||
|
||
import ( | ||
"crypto" | ||
"crypto/ecdsa" | ||
"crypto/rand" | ||
"crypto/sha256" | ||
"encoding/hex" | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"os" | ||
"net/http" | ||
"bytes" | ||
"bufio" | ||
"math/big" | ||
"encoding/base64" | ||
|
||
"github.com/pelicanplatform/pelican/config" | ||
) | ||
|
||
|
||
func signPayload(payload []byte, privateKey *ecdsa.PrivateKey) ([]byte, error) { | ||
hash := sha256.Sum256(payload) | ||
signature, err := privateKey.Sign(rand.Reader, hash[:], crypto.SHA256) // Use crypto.SHA256 instead of the hash[:] | ||
if err != nil { | ||
return nil, err | ||
} | ||
return signature, nil | ||
} | ||
|
||
func generateNonce() (string, error) { | ||
nonce := make([]byte, 32) | ||
_, err := rand.Read(nonce) | ||
if err != nil { | ||
return "", err | ||
} | ||
return hex.EncodeToString(nonce), nil | ||
} | ||
|
||
func make_request(url string, method string, data map[string]interface{}) ([]byte, error) { | ||
payload, _ := json.Marshal(data) | ||
|
||
req, err := http.NewRequest(method, url, bytes.NewBuffer(payload)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
req.Header.Set("Content-Type", "application/json") | ||
|
||
client := &http.Client{} | ||
resp, err := client.Do(req) | ||
if err != nil { | ||
return nil,err | ||
} | ||
defer resp.Body.Close() | ||
|
||
body, _ := ioutil.ReadAll(resp.Body) | ||
return body, nil | ||
} | ||
|
||
func resp_to_json(body []byte) (map[string]string) { | ||
// Unmarshal the response body | ||
var respData map[string]string | ||
err := json.Unmarshal(body, &respData) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return respData | ||
} | ||
|
||
func namespace_register_with_identity(publicKeyPath string, privateKeyPath string, namespaceRegistryEndpoint string, prefix string) (error) { | ||
data := map[string]interface{}{ | ||
"identity_required": "true", | ||
} | ||
resp, err := make_request(namespaceRegistryEndpoint, "POST", data) | ||
if err != nil { | ||
return fmt.Errorf("Failed to make request: %v\n", err) | ||
} | ||
respData := resp_to_json(resp) | ||
|
||
verification_url := respData["verification_url"] | ||
device_code := respData["device_code"] | ||
fmt.Printf("Verification URL: %s\n", verification_url) | ||
|
||
done := false | ||
for !done { | ||
data = map[string]interface{}{ | ||
"identity_required": "true", | ||
"device_code": device_code, | ||
} | ||
resp, err = make_request(namespaceRegistryEndpoint, "POST", data) | ||
if err != nil { | ||
return fmt.Errorf("Failed to make request: %v\n", err) | ||
} | ||
respData = resp_to_json(resp) | ||
|
||
if respData["status"] == "APPROVED" { | ||
done = true | ||
} else { | ||
fmt.Printf("Waiting for approval...\n") | ||
reader := bufio.NewReader(os.Stdin) | ||
fmt.Print("Press Enter after verification") | ||
_, _ = reader.ReadString('\n') | ||
} | ||
} | ||
access_token := respData["access_token"] | ||
fmt.Printf("Access token: %s\n", access_token) | ||
return namespace_register(publicKeyPath, privateKeyPath, namespaceRegistryEndpoint, access_token, prefix) | ||
} | ||
|
||
func namespace_register(publicKeyPath string, privateKeyPath string, namespaceRegistryEndpoint string, access_token string, prefix string) (error) { | ||
publicKey, err := config.LoadPublicKey(publicKeyPath, privateKeyPath) | ||
if err != nil { | ||
return fmt.Errorf("Failed to load public key: %v\n", err) | ||
} | ||
|
||
jwks, err := config.JWKSMap(publicKey) | ||
if err != nil { | ||
return fmt.Errorf("Failed to convert public key to JWKS: %v\n", err) | ||
} | ||
|
||
privateKey, err := config.LoadPrivateKey(privateKeyPath) | ||
if err != nil { | ||
return fmt.Errorf("Failed to load private key: %v\n", err) | ||
} | ||
|
||
client_nonce, err := generateNonce() | ||
if err != nil { | ||
return fmt.Errorf("Failed to generate client nonce: %v\n", err) | ||
} | ||
|
||
data := map[string]interface{}{ | ||
"client_nonce": client_nonce, | ||
"pubkey": fmt.Sprintf("%x", jwks["x"]), | ||
} | ||
|
||
resp, err := make_request(namespaceRegistryEndpoint, "POST", data) | ||
if err != nil { | ||
return fmt.Errorf("Failed to make request: %v\n", err) | ||
} | ||
respData := resp_to_json(resp) | ||
|
||
// Create client payload by concatenating client_nonce and server_nonce | ||
clientPayload := client_nonce + respData["server_nonce"] | ||
|
||
// Sign the payload | ||
signature, err := signPayload([]byte(clientPayload), privateKey) | ||
if err != nil { | ||
return fmt.Errorf("Failed to sign payload: %v\n", err) | ||
} | ||
|
||
// Create data for the second POST request | ||
xBytes, _ := base64.RawURLEncoding.DecodeString(jwks["x"]) | ||
yBytes, _ := base64.RawURLEncoding.DecodeString(jwks["y"]) | ||
|
||
data2 := map[string]interface{}{ | ||
"client_nonce": client_nonce, | ||
"server_nonce": respData["server_nonce"], | ||
"pubkey": map[string]string{ | ||
"x" : new(big.Int).SetBytes(xBytes).String(), | ||
"y" : new(big.Int).SetBytes(yBytes).String(), | ||
"curve": jwks["crv"], | ||
}, | ||
"client_payload": clientPayload, | ||
"client_signature": hex.EncodeToString(signature), | ||
"server_payload": respData["server_payload"], | ||
"server_signature": respData["server_signature"], | ||
"prefix": prefix, | ||
"access_token": access_token, | ||
} | ||
|
||
// Send the second POST request | ||
_, err = make_request(namespaceRegistryEndpoint, "POST", data2) | ||
if err != nil { | ||
return fmt.Errorf("Failed to make request: %v\n", err) | ||
} | ||
return nil | ||
} | ||
|
||
func list_namespaces(endpoint string) (error) { | ||
respData, err := make_request(endpoint, "GET", nil) | ||
if err != nil { | ||
return fmt.Errorf("Failed to make request: %v\n", err) | ||
} | ||
fmt.Println(string(respData)) | ||
return nil | ||
} | ||
|
||
func get_namespace(endpoint string) (error) { | ||
respData, err := make_request(endpoint, "GET", nil) | ||
if err != nil { | ||
return fmt.Errorf("Failed to make request: %v\n", err) | ||
} | ||
fmt.Println(string(respData)) | ||
return nil | ||
} | ||
|
||
func delete_namespace(endpoint string) (error) { | ||
respData, err := make_request(endpoint, "DELETE", nil) | ||
if err != nil { | ||
return fmt.Errorf("Failed to make request: %v\n", err) | ||
} | ||
fmt.Println(string(respData)) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.