Working through authentication, role and policy basics

This commit is contained in:
2026-04-02 01:51:48 -04:00
parent ace452bce3
commit a70c88342b
7 changed files with 516 additions and 62 deletions

View File

@@ -5,25 +5,13 @@ import (
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"libshared"
"log" "log"
"net/http" "net/http"
"os" "os"
"path/filepath"
) )
/*
func authMain(args []string) {
authCmd := flag.NewFlagSet("auth", flag.ExitOnError)
token := authCmd.String("t", "", "Auth token")
verbose := authCmd.Bool("v", false, "Verbose output")
authCmd.Parse(args)
fmt.Println("Auth command")
fmt.Println("Token:", *token)
fmt.Println("Verbose:", *verbose)
}
*/
var authCmd *flag.FlagSet var authCmd *flag.FlagSet
type AuthenticateRequest struct { type AuthenticateRequest struct {
@@ -32,53 +20,34 @@ type AuthenticateRequest struct {
Password string `json:"password"` Password string `json:"password"`
} }
type AuthenticateResponse struct {
Token string `json:"token"`
}
func authLocalGetToken() { func authLocalGetToken() {
} }
func authLocalAuthenticate(args []string) { func authLocalAuthenticate(args []string) {
var authSource string
var authUsername string var authUsername string
var authPassword string var authPassword string
var authAccountid string var authAccountid string
var outputFormat string var outputFormat string
var saveProfile string
authCmd.StringVar(&authSource, "s", "", "Source (required)") authCmd.StringVar(&saveProfile, "save-profile", "", "Save authentication profile with given name")
authCmd.StringVar(&authSource, "source", "", "Source (required)")
authCmd.StringVar(&authUsername, "u", "", "Username (required)") authCmd.StringVar(&authUsername, "u", "", "Username (required)")
authCmd.StringVar(&authUsername, "username", "", "Username (required)") authCmd.StringVar(&authUsername, "username", "", "Username (required)")
authCmd.StringVar(&authPassword, "p", "", "Password (required)") authCmd.StringVar(&authPassword, "p", "", "Password (required)")
authCmd.StringVar(&authPassword, "password", "", "Password (required)") authCmd.StringVar(&authPassword, "password", "", "Password (required)")
authCmd.StringVar(&authAccountid, "a", "", "Account ID (required)") authCmd.StringVar(&authAccountid, "a", "", "Account ID (required)")
authCmd.StringVar(&authAccountid, "account", "", "Account ID (required)") authCmd.StringVar(&authAccountid, "account", "", "Account ID (required)")
authCmd.StringVar(&outputFormat, "o", "text", "Output format (text or json)") authCmd.StringVar(&outputFormat, "o", "json", "Output format (text or json)")
// verbose := authCmd.Bool("v", false, "Verbose output") outputFormat = "json"
outputFormat = "text"
authCmd.Parse(args) authCmd.Parse(args)
if authSource == "" {
fmt.Println("Error: either -s or --source is required")
//authCmd.Usage()
os.Exit(1)
}
// Enforce required flag
if authSource == "" {
fmt.Println("Error: either -s or --source is required")
//authCmd.Usage()
os.Exit(1)
}
if authSource != "local" {
fmt.Println("Error: invalid source. Allowed values: local")
//authCmd.Usage()
os.Exit(1)
}
if authAccountid == "" { if authAccountid == "" {
fmt.Print("Account ID: ") fmt.Print("Account ID: ")
fmt.Scanln(&authAccountid) fmt.Scanln(&authAccountid)
@@ -91,6 +60,11 @@ func authLocalAuthenticate(args []string) {
if authPassword == "" { if authPassword == "" {
fmt.Print("Password: ") fmt.Print("Password: ")
fmt.Scanln(&authPassword) fmt.Scanln(&authPassword)
}
if (outputFormat != "json") && (outputFormat != "text") && (outputFormat != "silent") {
log.Fatal("Invalid output format. Use 'json', 'text', or 'silent'.")
} }
var authenticaterequest AuthenticateRequest var authenticaterequest AuthenticateRequest
@@ -125,30 +99,58 @@ func authLocalAuthenticate(args []string) {
log.Fatal("Authentication failed with status:", resp.Status) log.Fatal("Authentication failed with status:", resp.Status)
} }
var result map[string]interface{} apiresponse := libshared.APIResponse[AuthenticateResponse]{}
err = json.NewDecoder(resp.Body).Decode(&result)
// var result map[string]interface{}
err = json.NewDecoder(resp.Body).Decode(&apiresponse)
if err != nil { if err != nil {
log.Fatal("Error decoding response:", err) log.Fatal("Error decoding response:", err)
} }
if outputFormat == "json" { if saveProfile != "" {
jsonOutput, err := json.MarshalIndent(result, "", " ")
home, err := os.UserHomeDir()
if err != nil {
panic(err)
}
dir := filepath.Dir(home + "/.pcloud/identities/" + saveProfile + ".json")
err = os.MkdirAll(dir, 0700)
if err != nil {
log.Fatal("Error creating directory:", err)
}
profileData, err := json.MarshalIndent(apiresponse.Content, "", "\t")
if err != nil {
log.Fatal("Error encoding profile JSON:", err)
}
err = os.Remove(home + "/.pcloud/identities/" + saveProfile + ".json")
if err != nil && !os.IsNotExist(err) {
log.Fatal("Error removing profile:", err)
}
err = os.WriteFile(home+"/.pcloud/identities/"+saveProfile+".json", profileData, 0600)
if err != nil {
log.Fatal("Error saving profile:", err)
}
}
switch outputFormat {
case "json":
jsonOutput, err := json.MarshalIndent(apiresponse.Content, "", "\t")
if err != nil { if err != nil {
log.Fatal("Error encoding JSON output:", err) log.Fatal("Error encoding JSON output:", err)
} }
fmt.Println(string(jsonOutput)) fmt.Println(string(jsonOutput))
return case "text":
} else { fmt.Printf("Status: %s\nMessage: %s\n", apiresponse.Status, apiresponse.Message)
for key, value := range result { case "silent":
fmt.Printf("%s: %v\n", key, value)
}
}
}
} }
func authenticateMain(args []string) { func authenticateMain(args []string) {
authCmd = flag.NewFlagSet("auth", flag.ExitOnError) authCmd = flag.NewFlagSet("auth", flag.ExitOnError)
authLocalAuthenticate(args) authLocalAuthenticate(args)
//fmt.Println("Verbose:", *verbose)
} }

4
go.mod
View File

@@ -1,3 +1,7 @@
module upc module upc
go 1.25.0 go 1.25.0
require golang.org/x/term v0.41.0
require golang.org/x/sys v0.42.0 // indirect

4
go.sum Normal file
View File

@@ -0,0 +1,4 @@
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=

21
main.go
View File

@@ -16,17 +16,20 @@ type Command struct {
var commands = []Command{ var commands = []Command{
{ {
Names: []string{"login", "l"}, Names: []string{"role"},
Description: "Log into the system", Description: "Role Management",
Handler: func(args []string) { Handler: roleMain,
fmt.Println("Executing login with args:", args)
},
}, },
{ {
Names: []string{"auth", "authenticate"}, Names: []string{"auth", "authorization"},
Description: "Authenticate with a token", Description: "Role Management",
Handler: authenticateMain, Handler: authenticateMain,
}, },
{
Names: []string{"policy"},
Description: "Policy Management",
Handler: policyMain,
},
} }
func printMainUsage() { func printMainUsage() {
@@ -43,7 +46,7 @@ func formatNames(names []string) string {
return strings.Join(names, ", ") return strings.Join(names, ", ")
} }
func findCommand(name string) *Command { func findCommand(name string, commands []Command) *Command {
for _, cmd := range commands { for _, cmd := range commands {
for _, n := range cmd.Names { for _, n := range cmd.Names {
if n == name { if n == name {
@@ -64,7 +67,7 @@ func main() {
subcommand := os.Args[1] subcommand := os.Args[1]
cmd := findCommand(subcommand) cmd := findCommand(subcommand, commands)
if cmd == nil { if cmd == nil {
fmt.Println("Error: unknown command:", subcommand) fmt.Println("Error: unknown command:", subcommand)
printMainUsage() printMainUsage()

204
policy.go Normal file
View File

@@ -0,0 +1,204 @@
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"net/http"
"os"
)
var policyCmd *flag.FlagSet
var policyCommands = []Command{
{
Names: []string{"create-policy"},
Description: "Create Policy",
Handler: policyCreate,
},
{
Names: []string{"list-policies"},
Description: "List Policies",
Handler: policyCreate,
},
}
// Condition represents each item in the "conditions" array
type Condition struct {
StatementID string `json:"statementid"`
Principal []string `json:"principals"`
Actions []string `json:"actions"`
Source []string `json:"source"`
Effect string `json:"effect"`
Operator string `json:"operator"`
}
type Policy struct {
Name string `json:"name"`
Description string `json:"description"`
Conditions []Condition `json:"conditions"`
}
type CreatePolicyRequest struct {
Name string `json:"name"`
Description string `json:"description"`
PolicyDocument Policy `json:"PolicyDocument"`
}
type CreatePolicyResponse struct {
PolicyID string `json:"policy_id"`
}
type ProfileToken struct {
Token string `json:"token"`
}
type IdentityToken struct {
Token string `json:"token"`
}
func policyCreate(args []string) {
policyCmd := flag.NewFlagSet("create-policy", flag.ExitOnError)
var policyname string
var useprofile string
var policyjson string
var policyfile string
var policyDescription string
var createpolicyrequest CreatePolicyRequest
var normalizedDocument string
policyCmd.StringVar(&policyname, "name", "", "Policy Name (required)")
policyCmd.StringVar(&policyDescription, "description", "", "Policy Description")
policyCmd.StringVar(&useprofile, "profile", "", "Profile")
policyCmd.StringVar(&policyjson, "policy-json", "", "Policy JSON")
policyCmd.StringVar(&policyfile, "policy-file", "", "JSON Policy File")
policyCmd.Parse(args)
if policyname == "" {
fmt.Println("Error: either -n or --name is required")
os.Exit(1)
}
if useprofile == "" {
fmt.Println("Error: either -profile or --profile is required")
os.Exit(1)
}
if policyjson == "" && policyfile == "" {
fmt.Println("Error: either -p/--policy-json or -f/--policy-file is required")
os.Exit(1)
}
if policyjson != "" && policyfile != "" {
fmt.Println("Error: only one of -p/--policy-json or -f/--policy-file can be provided")
os.Exit(1)
}
// Open Profile file
home, err := os.UserHomeDir()
if err != nil {
panic(err)
}
profileData, err := os.ReadFile(home + "/.pcloud/profiles/" + useprofile + ".json")
if err != nil {
fmt.Printf("Error opening profile file: %v\n", err)
os.Exit(1)
}
fmt.Println("Length is", len(profileData))
if len(profileData) == 0 {
fmt.Printf("Profile file is empty: %s\n", home+"/.pcloud/profiles/"+useprofile+".json")
os.Exit(1)
}
var profileToken ProfileToken
err = json.Unmarshal(profileData, &profileToken)
if err != nil {
fmt.Printf("Error reading profile file: %v\n", err)
os.Exit(1)
}
fmt.Println("Profile Token: ", profileToken.Token)
if policyfile != "" {
content, err := os.ReadFile(policyfile)
if err != nil {
fmt.Printf("Error reading policy file: %v\n", err)
os.Exit(1)
}
normalizedDocument = string(content)
} else if policyjson != "" {
normalizedDocument = policyjson
}
// Validate JSON
if !json.Valid([]byte(normalizedDocument)) {
fmt.Println("Error: invalid JSON for policy document")
os.Exit(1)
}
createpolicyrequest.Name = policyname
err = json.Unmarshal([]byte(normalizedDocument), &createpolicyrequest.PolicyDocument)
if err != nil {
fmt.Printf("Error reading policy document: %v\n", err)
os.Exit(1)
}
policyrequestData, err := json.Marshal(createpolicyrequest)
if err != nil {
fmt.Printf("Error encoding policy request: %v\n", err)
os.Exit(1)
}
fmt.Println("Policy Name:", createpolicyrequest.Name)
fmt.Println("Policy JSON:", createpolicyrequest.PolicyDocument)
apiendpoint := endpoint + "/policy/create-policy"
fmt.Println(apiendpoint)
req, err := http.NewRequest("POST", apiendpoint, bytes.NewBuffer(policyrequestData))
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+profileToken.Token)
fmt.Println("Using token:", profileToken.Token)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("Response status:", resp.Status, resp.Body)
}
func policyList(args []string) {
}
func policyMain(args []string) {
fmt.Println("Policy Main")
if len(args) < 1 {
fmt.Println("Error: subcommand is required")
os.Exit(1)
}
subcommand := args[0]
cmd := findCommand(subcommand, policyCommands)
if cmd == nil {
fmt.Println("Error: unknown command:", subcommand)
os.Exit(1)
}
cmd.Handler(args[1:])
}

5
request.go Normal file
View File

@@ -0,0 +1,5 @@
package main
func getReq() {
}

232
role.go Normal file
View File

@@ -0,0 +1,232 @@
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"libshared"
"net/http"
"os"
)
type AssumeRoleRequest struct {
Rolename string `json:"rolename"`
}
type AssumeRoleResponse struct {
Token string `json:"token"`
}
var roleattachpolicyCmd *flag.FlagSet
var roleCommands = []Command{
{
Names: []string{"attach-policy"},
Description: "Role Management",
Handler: roleAttachPolicy,
},
{
Names: []string{"detach-policy"},
Description: "Role Management",
Handler: roleDetachPolicy,
},
{
Names: []string{"assume-role"},
Description: "Role Management",
Handler: assumeRole,
},
}
func assumeRole(args []string) {
fmt.Println("I assume something")
var targetRole string
var useIdentity string
roleAssumeRoleCmd := flag.NewFlagSet("assume-role", flag.ExitOnError)
roleAssumeRoleCmd.StringVar(&targetRole, "r", "", "Target role (required)")
roleAssumeRoleCmd.StringVar(&targetRole, "role", "", "Target role (required)")
roleAssumeRoleCmd.StringVar(&useIdentity, "identity", "", "Identity to use (required)")
roleAssumeRoleCmd.Parse(args)
if targetRole == "" {
fmt.Println("Error: either -r or --role is required")
os.Exit(1)
}
if useIdentity == "" {
fmt.Println("Error: either --identity is required")
os.Exit(1)
}
identityData, err := os.ReadFile("/home/farhan/.pcloud/identities/" + useIdentity + ".json")
if err != nil {
fmt.Printf("Error opening identity file: %v\n", err)
os.Exit(1)
}
var identityToken IdentityToken
err = json.Unmarshal(identityData, &identityToken)
if err != nil {
fmt.Printf("Error reading identity file: %v\n", err)
os.Exit(1)
}
apiendpoint := endpoint + "/role/assume-role"
var assumerolerequest AssumeRoleRequest
assumerolerequest.Rolename = targetRole
jsonData, err := json.Marshal(assumerolerequest)
if err != nil {
fmt.Println("Error marshaling JSON:", err)
return
}
req, err := http.NewRequest("POST", apiendpoint, bytes.NewBuffer(jsonData))
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+identityToken.Token)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
// var assumeRoleResponse AssumeRoleResponse
apiresponse := libshared.APIResponse[AssumeRoleResponse]{}
if err := json.NewDecoder(resp.Body).Decode(&apiresponse); err != nil {
fmt.Printf("Error reading response: %v\n", err)
os.Exit(1)
}
// fmt.Println("Response status:", resp.Status)
fmt.Println("Assumed role token:", apiresponse.Content.Token)
// fmt.Printf("Attaching policy '%s' to role '%s'\n", , targetRole)
fmt.Println("Response status:", resp.Status)
fmt.Printf("Assuming role '%s'\n", targetRole)
fmt.Println("Using identity", resp.Body)
// Write apiresponse.Content to ~/.pcloud/roles/assumed-<rolename>.json
home, err := os.UserHomeDir()
if err != nil {
panic(err)
}
dir := fmt.Sprintf("%s/.pcloud/roles", home)
err = os.MkdirAll(dir, 0700)
if err != nil {
fmt.Printf("Error creating directory: %v\n", err)
os.Exit(1)
}
roleData, err := json.MarshalIndent(apiresponse.Content, "", "\t")
if err != nil {
fmt.Printf("Error marshaling role data: %v\n", err)
os.Exit(1)
}
roleFile := fmt.Sprintf("%s/%s.json", dir, targetRole)
err = os.WriteFile(roleFile, roleData, 0600)
if err != nil {
fmt.Printf("Error writing role file: %v\n", err)
os.Exit(1)
}
fmt.Printf("Assumed role token saved to %s\n", roleFile)
}
func roleAttachPolicy(args []string) {
roleattachpolicyCmd := flag.NewFlagSet("attach-policy", flag.ExitOnError)
//var policyToAttach string
var targetRole string
var useIdentity string
roleattachpolicyCmd.StringVar(&targetRole, "role", "", "Target role (required)")
roleattachpolicyCmd.StringVar(&useIdentity, "identity", "", "Identity to use (default: default)")
roleattachpolicyCmd.Parse(args)
if targetRole == "" {
fmt.Println("Error: either --role is required")
os.Exit(1)
}
if useIdentity == "" {
fmt.Println("Error: either --identity is required")
os.Exit(1)
}
// Open Profile file
home, err := os.UserHomeDir()
if err != nil {
panic(err)
}
profileData, err := os.ReadFile(home + "/.pcloud/identities/" + useIdentity + ".json")
if err != nil {
fmt.Printf("Error opening profile file: %v\n", err)
os.Exit(1)
}
// fmt.Println("Length is", len(profileData))
if len(profileData) == 0 {
fmt.Printf("Profile file is empty: %s\n", home+"/.pcloud/identities/"+useIdentity+".json")
os.Exit(1)
}
var profileToken ProfileToken
err = json.Unmarshal(profileData, &profileToken)
if err != nil {
fmt.Printf("Error reading profile file: %v\n", err)
os.Exit(1)
}
fmt.Println("Profile Token: ", profileToken.Token)
apiendpoint := endpoint + "/role/attach-policy"
req, err := http.NewRequest("POST", apiendpoint, nil) //, bytes.NewBuffer(jsonData))
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer YOUR_TOKEN_HERE")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
}
func roleDetachPolicy(args []string) {
fmt.Println("I detach something")
}
func roleMain(args []string) {
if len(args) < 1 {
fmt.Println("Error: subcommand is required")
os.Exit(1)
}
subcommand := args[0]
cmd := findCommand(subcommand, roleCommands)
if cmd == nil {
fmt.Println("Error: unknown command:", subcommand)
os.Exit(1)
}
cmd.Handler(args[1:])
}