Skip to content

Commit

Permalink
Merge branch 'add-anonymous-client-mode' into main
Browse files Browse the repository at this point in the history
No issue #
  • Loading branch information
cure committed Jan 10, 2021
2 parents a508cf3 + c681fc2 commit 79d1296
Show file tree
Hide file tree
Showing 5 changed files with 368 additions and 133 deletions.
90 changes: 48 additions & 42 deletions doc/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

## Models

Example Config
Example Config (anonymous mode)

ListenHost: "the:yggdrasil:ip:address:of:the:autoygg:server"
ListenPort: 8080
GatewayOwner: "You <[email protected]>"
GatewayDescription: "This is an Yggdrasil gateway operated for fun and profit"
RequireRegistration: false
RequireApproval: false
AccessListEnabled: true
AccessListEnabled: false
StateDir: "/var/lib/autoygg"
MaxClients: 10
LeaseTimeoutSeconds: 14400
Expand All @@ -26,23 +25,14 @@ Registration Model
gorm.Model
YggIP string // Client Yggdrasil IP address
PublicKey string // Client Yggdrasil PublicKey
ClientName string // Registration name (optional)
ClientEmail string // Registration email (optional)
ClientPhone string // Registration phone (optional)
Error string
Approved Bool
}

Lease Model

type lease struct {
gorm.Model
YggIP string // Client Yggdrasil IP address
PublicKey string // Client Yggdrasil PublicKey
GatewayPublicKey string
ClientName string // Registration name (optional depending on operating mode)
ClientEmail string // Registration email (optional depending on operating mode)
ClientPhone string // Registration phone (optional depending on operating mode)
ClientIP string // The tunnel IP address assigned to the client
ClientNetMask int // The tunnel netmask
ClientGateway string
Error string
Approved Bool
LeaseExpires time.Time
}

Expand All @@ -56,43 +46,59 @@ ACL Model

## Operating Modes
### Full Anonymous
* Allows anybody to directly `GET /lease` to use the gateway, subject to ACL config
* Allows anybody to do `POST /register` without sending personal information
* Access granted automatically
* RequireRegistration = false
* AccessListEnabled = false

### Registration
* Requires all gateway users to first `POST /register` to store personal information with the gateway before requesting `POST /lease`
* Requires all users to do `POST /register` with personal information (name, phone, e-mail)
* Access granted automatically
* RequireRegistration = true
* RequireApproval = false
* AccessListEnabled = false

### Registration & Approval
* Requires all gateway users to `POST /register` and wait for the gateway admin to manually approve the registration before the user is allowed to `POST /lease`
* Requires all users to do `POST /register` with personal information (name, phone, e-mail)
* Must wait for the gateway admin to manually approve the registration to use the gateway by adding an entry to the AccessList
* RequireRegistration = true
* RequireApproval = true
* AccessListEnabled = true

### Full Anonymous & Approval
* Allows anybody to do `POST /register` without sending personal information
* Must wait for the gateway admin to manually approve the registration to use the gateway by adding an entry to the AccessList
* RequireRegistration = false
* AccessListEnabled = true

## ACL Modes
### ACL disabled
* Allows anyone with a valid registration to use the gateway
* AccessListEnabled = false

### ACL enabled
* Allows only valid registrations with an ACL entry set to `access: true` to use the gateway
* AccessListEnabled = true

## ACL Check Routine:
* If ACL entry exists for client IP with Access: false
* Return access error
* If AccessListEnabled=true and ACL entry does not exist for client IP with Access: true
* Return access error

## Endpoints
* `GET /info`: Returns GatewayOwner, Description, RequireRegistration, ACLEnabled
* `GET /info`: Returns GatewayOwner, Description, RequireRegistration, AccessListEnabled
* `GET /register`:
* Return access error if ACL check fails
* If RequireRegistration=false: Disabled
* If RequireRegistration=true: Return registration status for user if found or 404
* If AccessListEnabled=true, apply ACLs, return access error if access denied
* If Registration is found, return status, otherwise return error
* `POST /register`:
* Return access error if ACL check fails
* If RequireRegistration=false, Disabled
* If ACLEnabled=true, apply ACLFile based on ACLMode, give access error if conditions not met
* If RequireRegistration=true, Store registration information with Approved=false
* Storing unapproved feels like the safer thing to do in case someone switches RequireApproval on and off
* If AccessListEnabled=true, apply ACLs, return access error if access denied
* If RequireRegistration=true: require ClientName, ClientEmail, ClientPhone to be populated, otherwise return error
* Create Registration, provision client
* `POST /renew`:
* Return access error if ACL check fails
* If RequireRegistration=true: Deny unless approved registration found
* If ACLEnabled=true, apply ACLFile based on ACLMode, give access error if conditions not met
* Assign lease, provision lease, and store in leases table
* If AccessListEnabled=true, apply ACLs, return access error if access denied
* If RequireRegistration=true: require ClientName, ClientEmail, ClientPhone to be populated, otherwise return error
* If Registration is found, extend lease expiry date, otherwise return error
* `POST /release`:
* Return access error if ACL check fails
* Remove lease from leases, teardown lease, and return success. Return 404 if lease doesn't exist
* ACL Check Routine:
* If acl entry exists for client IP with Access: false
* Return access error
* If AccessListEnabled=true and acl entry does not exist for client IP with Access: true
* Return access error
* If AccessListEnabled=true, apply ACLs, return access error if access denied
* Remove Registration, unprovision client, and return success. Return 404 if lease doesn't exist

# Client Operating Model
75 changes: 48 additions & 27 deletions internal/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ Options:
fmt.Fprintln(os.Stderr, "")
}

func doRequestWorker(fs *flag.FlagSet, verb string, action string, gatewayHost string, gatewayPort string) (response []byte, err error) {
func doRequestWorker(fs *flag.FlagSet, verb string, action string, gatewayHost string, gatewayPort string, i info) (response []byte, err error) {
validActions := map[string]bool{
"register": true,
"renew": true,
"release": true,
"register": true, // register and request a lease
"renew": true, // renew an existing lease
"release": true, // release an existing lease
}
if !validActions[action] {
err = errors.New("Invalid action: " + action)
Expand All @@ -92,9 +92,12 @@ func doRequestWorker(fs *flag.FlagSet, verb string, action string, gatewayHost s
if err != nil {
return
}
r.ClientName = cViper.GetString("clientname")
r.ClientEmail = cViper.GetString("clientemail")
r.ClientPhone = cViper.GetString("clientphone")
// Only send ClientName, ClientEmail and ClientPhone when registration is required
if i.RequireRegistration {
r.ClientName = cViper.GetString("clientname")
r.ClientEmail = cViper.GetString("clientemail")
r.ClientPhone = cViper.GetString("clientphone")
}
r.ClientVersion = version
req, err := json.Marshal(r)
if err != nil {
Expand Down Expand Up @@ -285,7 +288,7 @@ func clientLoadConfig(path string) {
}
}

func clientCreateFlagSet() (fs *flag.FlagSet) {
func clientCreateFlagSet(args []string) (fs *flag.FlagSet) {
fs = flag.NewFlagSet("Autoygg", flag.ContinueOnError)
fs.Usage = func() { clientUsage(fs) }

Expand All @@ -310,7 +313,7 @@ func clientCreateFlagSet() (fs *flag.FlagSet) {
fs.Bool("help", false, "print usage and exit")
fs.Bool("version", false, "print version and exit")

err := fs.Parse(os.Args[1:])
err := fs.Parse(args)
if err != nil {
Fatal(err)
}
Expand All @@ -334,7 +337,7 @@ func doInfoRequest(fs *flag.FlagSet, gatewayHost string, gatewayPort string) (i
client := http.Client{
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: 200 * time.Millisecond,
Timeout: 500 * time.Millisecond,
}).Dial,
},
}
Expand All @@ -356,8 +359,17 @@ func doInfoRequest(fs *flag.FlagSet, gatewayHost string, gatewayPort string) (i

func doRequest(fs *flag.FlagSet, action string, gatewayHost string, gatewayPort string, State state) (r registration, newState state, err error) {
newState = State

// Do an info request to know if registration is required
i, err := handleInfoWorker(fs)
if err != nil {
handleError(err, cViper, false)
return
}

verb := "post"
log.Printf("Sending `" + action + "` request to autoygg")
response, err := doRequestWorker(fs, "post", action, gatewayHost, gatewayPort)
response, err := doRequestWorker(fs, verb, action, gatewayHost, gatewayPort, i)
if err != nil {
handleError(err, cViper, false)
return
Expand Down Expand Up @@ -434,7 +446,7 @@ func saveState(State state) {
}

func clientValidateConfig() (fs *flag.FlagSet) {
fs = clientCreateFlagSet()
fs = clientCreateFlagSet(os.Args[1:])

if cViper.GetBool("UseConfig") {
cViper.SetConfigType("yaml")
Expand Down Expand Up @@ -487,27 +499,29 @@ func clientValidateConfig() (fs *flag.FlagSet) {
return
}

func handleInfo(fs *flag.FlagSet) {
i, err := doInfoRequest(fs, cViper.GetString("GatewayHost"), cViper.GetString("GatewayPort"))
func handleInfoWorker(fs *flag.FlagSet) (i info, err error) {
i, err = doInfoRequest(fs, cViper.GetString("GatewayHost"), cViper.GetString("GatewayPort"))
if err != nil {
if os.IsTimeout(err) {
logAndExit(fmt.Sprintf("Timeout: could not connect to gateway at %s", cViper.GetString("GatewayHost")), 1)
} else {
logAndExit(err.Error(), 1)
err = fmt.Errorf("Timeout: could not connect to gateway at %s", cViper.GetString("GatewayHost"))
}
}
json, err := json.MarshalIndent(i, "", " ")
return
}

func handleInfo(fs *flag.FlagSet, i info) {
infoJson, err := json.MarshalIndent(i, "", " ")
if err != nil {
logAndExit(err.Error(), 1)
}
fmt.Printf("%s\n", json)
fmt.Printf("%s\n", infoJson)
os.Exit(0)
}

// ClientMain is the main() function for the client program
func ClientMain() {
cViper = viper.New()
setupLogWriters(cViper)
setupLogWriters(cViper, true)

fs := clientValidateConfig()

Expand Down Expand Up @@ -537,14 +551,21 @@ func ClientMain() {
}

if cViper.GetString("Action") == "info" {
handleInfo(fs)
} else if cViper.GetString("Action") == "register" {
State.DesiredState = "connected"
} else if cViper.GetString("Action") == "release" {
State.DesiredState = "disconnected"
State, err = clientTearDownRoutes(State.ClientIP, State.ClientNetMask, State.ClientGateway, State.GatewayPublicKey, State)
i, err := handleInfoWorker(fs)
// if the 'info' request failed bail out here
if err != nil {
Fatal(err)
logAndExit(err.Error(), 1)
}
handleInfo(fs, i)
} else {
if cViper.GetString("Action") == "register" || cViper.GetString("Action") == "renew" {
State.DesiredState = "connected"
} else if cViper.GetString("Action") == "release" {
State.DesiredState = "disconnected"
State, err = clientTearDownRoutes(State.ClientIP, State.ClientNetMask, State.ClientGateway, State.GatewayPublicKey, State)
if err != nil {
Fatal(err)
}
}
}

Expand Down
Loading

0 comments on commit 79d1296

Please sign in to comment.