Skip to content

Commit

Permalink
Add credentials index test
Browse files Browse the repository at this point in the history
  • Loading branch information
kiootic committed Sep 18, 2023
1 parent 6b089b4 commit b86e33f
Show file tree
Hide file tree
Showing 6 changed files with 331 additions and 98 deletions.
54 changes: 54 additions & 0 deletions docs/references/access-control.md
Original file line number Diff line number Diff line change
@@ -1 +1,55 @@
# Access Control

Access control is configured by ACL rules of different types. A request/action
passes the access control check if it matches any of the applicable ACL rules.

A typical ACL would looks like this:
```toml
access = [
{ githubUser="username" },
{ ipRange="127.0.0.1/32" }
]
```

## Authentication

A GitHub user may authenticate through the `pageship login` command. Currently,
it will connect to the Pageship server through SSH protocol, and verify user's
identity through GitHub user's public key.

GitHub Actions jobs would be authenticate automatically when `pageship` command
detected running in CI environment. It authenticates through GitHub Actions
OIDC token.

## ACL Types

### GitHub user

```toml
{ githubUser = "username" }
```

Actions/requests from the specified GitHub user is allowed.

### GitHub Actions repository
```toml
{ gitHubRepositoryActions = "oursky/pageship" }
{ gitHubRepositoryActions = "oursky/*" }
{ gitHubRepositoryActions = "*" }
```

Actions/requests from the specified GitHub Action jobs is allowed. Wildcard can
be specified for all repository of a user/organization, or any repository.


### IP Range

```toml
{ ipRange = "127.0.0.1/32" }
{ ipRange = "192.168.0.0/16" }
{ ipRange = "0.0.0.0/0" }
{ ipRange = "::1/128" }
```

Actions/requests from the specified IP range (CIDR) is allowed.
IPv4 is mapped to IPv6 before matching.
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/sts v1.18.3 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/chzyer/readline v1.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
Expand Down Expand Up @@ -108,11 +109,14 @@ require (
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1965,6 +1965,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
Expand All @@ -1979,6 +1980,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
Expand Down
98 changes: 0 additions & 98 deletions internal/models/credential_id.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package models

import (
"encoding/hex"
"net/netip"
"strings"

Expand Down Expand Up @@ -76,100 +75,3 @@ func (c CredentialID) Matches(r *config.ACLSubjectRule) bool {
return false
}
}

type CredentialIndexKey string

func MakeCredentialIDIndexKeys(id CredentialID) []CredentialIndexKey {
kind, data, found := strings.Cut(string(id), ":")
if !found {
kind = ""
}

switch CredentialIDKind(kind) {
case CredentialIDKindUserID,
CredentialIDKindGitHubUser:
return []CredentialIndexKey{CredentialIndexKey(id)}

case CredentialIDGitHubRepositoryActions:
return []CredentialIndexKey{
CredentialIndexKey(id),
CredentialIndexKey(CredentialIDGitHubRepositoryActions + ":*"),
}

case CredentialIDIP:
addr, err := netip.ParseAddr(data)
if err != nil {
return nil
}

addr = netip.AddrFrom16(addr.As16())
return makeIPKeys(addr, addr.BitLen())

default:
return nil
}
}

func CollectCredentialIDIndexKeys(ids []CredentialID) []CredentialIndexKey {
var keys []CredentialIndexKey
for _, id := range ids {
keys = append(keys, MakeCredentialIDIndexKeys(id)...)
}
return keys
}

func MakeCredentialRuleIndexKeys(r *config.ACLSubjectRule) []CredentialIndexKey {
switch {
case r.PageshipUser != "":
return MakeCredentialIDIndexKeys(CredentialUserID(r.PageshipUser))
case r.GitHubUser != "":
return MakeCredentialIDIndexKeys(CredentialGitHubUser(r.GitHubUser))
case r.GitHubRepositoryActions != "":
return MakeCredentialIDIndexKeys(CredentialGitHubRepositoryActions(r.GitHubRepositoryActions))
case r.IpRange != "":
cidr, err := netip.ParsePrefix(r.IpRange)
if err != nil {
return nil
}

bits := cidr.Bits()
addr := netip.AddrFrom16(cidr.Addr().As16())
if cidr.Addr().Is4() {
// Map ipv4 CIDR to ipv6.
bits += 96
}

keys := makeIPKeys(addr, bits)
return []CredentialIndexKey{keys[len(keys)-1]} // Use longest key (i.e. last key)
}
return nil
}

func makeIPKeys(addr netip.Addr, bits int) []CredentialIndexKey {
addrBytes := addr.As16()
bytes := addrBytes[:(bits/8)&(^1)]

keys := []CredentialIndexKey{"ip:"}
var s strings.Builder
zeroes := 0
for b := 0; b < len(bytes); b += 2 {
if bytes[b] == 0 && bytes[b+1] == 0 {
zeroes++
continue
}

if zeroes != 0 {
s.WriteByte(':')
zeroes = 0
}
if b != 0 {
s.WriteByte(':')
}
s.WriteString(hex.EncodeToString(bytes[b : b+2]))
keys = append(keys, CredentialIndexKey("ip:"+s.String()))
}
if zeroes > 0 {
keys = append(keys, CredentialIndexKey("ip:"+s.String()+":"))
}
return keys
}
109 changes: 109 additions & 0 deletions internal/models/credential_index.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package models

import (
"encoding/hex"
"net/netip"
"strings"

"github.com/oursky/pageship/internal/config"
)

type CredentialIndexKey string

func MakeCredentialIDIndexKeys(id CredentialID) []CredentialIndexKey {
kind, data, found := strings.Cut(string(id), ":")
if !found {
kind = ""
}

switch CredentialIDKind(kind) {
case CredentialIDKindUserID,
CredentialIDKindGitHubUser:
return []CredentialIndexKey{CredentialIndexKey(id)}

case CredentialIDGitHubRepositoryActions:
owner, repo, ok := strings.Cut(data, "/")
if !ok {
return nil
}
prefix := string(CredentialIDGitHubRepositoryActions) + ":"
return []CredentialIndexKey{
CredentialIndexKey(prefix + "*"),
CredentialIndexKey(prefix + owner),
CredentialIndexKey(prefix + owner + "/" + repo),
}

case CredentialIDIP:
addr, err := netip.ParseAddr(data)
if err != nil {
return nil
}

addr = netip.AddrFrom16(addr.As16())
return makeIPKeys(addr, addr.BitLen())

default:
return nil
}
}

func CollectCredentialIDIndexKeys(ids []CredentialID) []CredentialIndexKey {
var keys []CredentialIndexKey
for _, id := range ids {
keys = append(keys, MakeCredentialIDIndexKeys(id)...)
}
return keys
}

func MakeCredentialRuleIndexKeys(r *config.ACLSubjectRule) []CredentialIndexKey {
switch {
case r.PageshipUser != "":
return MakeCredentialIDIndexKeys(CredentialUserID(r.PageshipUser))
case r.GitHubUser != "":
return MakeCredentialIDIndexKeys(CredentialGitHubUser(r.GitHubUser))
case r.GitHubRepositoryActions != "":
prefix := string(CredentialIDGitHubRepositoryActions) + ":"
if r.GitHubRepositoryActions == "*" {
return []CredentialIndexKey{CredentialIndexKey(prefix + "*")}
}

owner, repo, ok := strings.Cut(r.GitHubRepositoryActions, "/")
if !ok {
return nil
}
if repo == "*" {
return []CredentialIndexKey{CredentialIndexKey(prefix + owner)}
} else {
return []CredentialIndexKey{CredentialIndexKey(prefix + owner + "/" + repo)}
}
case r.IpRange != "":
cidr, err := netip.ParsePrefix(r.IpRange)
if err != nil {
return nil
}

bits := cidr.Bits()
addr := netip.AddrFrom16(cidr.Addr().As16())
if cidr.Addr().Is4() {
// Map ipv4 CIDR to ipv6.
bits += 96
}

keys := makeIPKeys(addr, bits)
return []CredentialIndexKey{keys[len(keys)-1]} // Use longest key (i.e. last key)
}
return nil
}

func makeIPKeys(addr netip.Addr, bits int) []CredentialIndexKey {
addrBytes := addr.As16()
bytes := addrBytes[:(bits/8)&(^1)]

keys := []CredentialIndexKey{"ip:"}
var s strings.Builder
for b := 0; b < len(bytes); b += 2 {
s.WriteString(hex.EncodeToString(bytes[b : b+2]))
keys = append(keys, CredentialIndexKey("ip:"+s.String()))
}
return keys
}
Loading

0 comments on commit b86e33f

Please sign in to comment.