Skip to content
This repository has been archived by the owner on Sep 30, 2024. It is now read-only.

Add PowerAuthGroups: users in these unix groups get admin access #215

Merged
merged 4 commits into from
Jun 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ type Configuration struct {
HTTPAuthPassword string // Password for HTTP Basic authentication
AuthUserHeader string // HTTP header indicating auth user, when AuthenticationMethod is "proxy"
PowerAuthUsers []string // On AuthenticationMethod == "proxy", list of users that can make changes. All others are read-only.
PowerAuthGroups []string // list of unix groups the authenticated user must be a member of to make changes.
AccessTokenUseExpirySeconds uint // Time by which an issued token must be used
AccessTokenExpiryMinutes uint // Time after which HTTP access token expires
ClusterNameToAlias map[string]string // map between regex matching cluster name to a human friendly alias
Expand Down Expand Up @@ -296,6 +297,7 @@ func newConfiguration() *Configuration {
HTTPAuthPassword: "",
AuthUserHeader: "X-Forwarded-User",
PowerAuthUsers: []string{"*"},
PowerAuthGroups: []string{},
AccessTokenUseExpirySeconds: 60,
AccessTokenExpiryMinutes: 1440,
ClusterNameToAlias: make(map[string]string),
Expand Down
5 changes: 5 additions & 0 deletions go/http/httpbase.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/martini-contrib/auth"

"github.com/github/orchestrator/go/config"
"github.com/github/orchestrator/go/os"
"github.com/github/orchestrator/go/process"
)

Expand Down Expand Up @@ -64,6 +65,10 @@ func isAuthorizedForAction(req *http.Request, user auth.User) bool {
return true
}
}
// check the user's group is one of those listed here
if len(config.Config.PowerAuthGroups) > 0 && os.UserInGroups(authUser, config.Config.PowerAuthGroups) {
return true
}
return false
}
case "token":
Expand Down
68 changes: 68 additions & 0 deletions go/os/unixcheck.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
Copyright 2017 Simon Mudd, courtesy Booking.com

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package os

import (
"os/user"
"strings"

"github.com/openark/golib/log"
)

// UserInGroups checks if the given username is in the given unix
// groups. It might be worth caching this for performance reasons.
func UserInGroups(authUser string, powerAuthGroups []string) bool {
// these conditions are treated as false
if authUser == "" || len(powerAuthGroups) == 0 {
return false
}

// make a map (likely to have only one group maybe) of power groups
powerGroupMap := make(map[string]bool)
for _, v := range powerAuthGroups {
powerGroupMap[v] = true
}

currentUser, err := user.Lookup(authUser)
if err != nil {
// The user not being known is not an error so don't report this.
// ERROR Failed to lookup user "simon": user: unknown user simon
if !strings.Contains(err.Error(), "unknown user") {
log.Errorf("Failed to lookup user %q: %v", authUser, err)
}
return false
}
gids, err := currentUser.GroupIds()
if err != nil {
log.Errorf("Failed to lookup groupids for user %q: %v", authUser, err)
return false
}
// get the group name from the id and check if the name is in powerGroupMap
for _, gid := range gids {
group, err := user.LookupGroupId(gid)
if err != nil {
log.Errorf("Failed to lookup group id for gid %s: %v", gid, err) // yes gids are strings!
return false
}

if _, found := powerGroupMap[group.Name]; found {
return true
}
}

return false
}
49 changes: 49 additions & 0 deletions go/os/unixcheck_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
Copyright 2017 Simon Mudd, courtesy Booking.com

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package os

import (
"testing"
)

type testCase struct {
user string
powerUsers []string
expected bool
}

var testCases []testCase

func init() {
// It is hard to come up with good results that will work on all systems
// so the tests are limited but should work on most Linux or OSX systems.
// If you find a case where the tests fail due to user differences please
// adjust the test cases appropriately.
testCases = []testCase{
{"root", []string{"root", "wheel"}, true},
{"root", []string{"not_in_this_group"}, false},
{"not_found_user", []string{"not_in_this_group"}, false},
}
}

// test the users etc
func TestUsers(t *testing.T) {
for _, v := range testCases {
if got := UserInGroups(v.user, v.powerUsers); got != v.expected {
t.Errorf("userInGroups(%q,%+v) failed. Got %v, Expected %v", v.user, v.powerUsers, got, v.expected)
}
}
}