Skip to content

Commit

Permalink
group commands for users and servers
Browse files Browse the repository at this point in the history
  • Loading branch information
Sam Ban committed Sep 4, 2021
1 parent faa0e5e commit c429ab0
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 18 deletions.
24 changes: 24 additions & 0 deletions .github/workflows/push.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Develop Test

on:
push:
branches-ignore:
- wip

jobs:
Test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Checkout submodules
uses: textbook/git-checkout-submodule-action@master
with:
remote: true
- name: Install Go
uses: actions/setup-go@v1
with:
go-version: '1.17.x'
- name: Checkout code
uses: actions/checkout@v1
- name: Tests
run: make test
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# SSH Manager - manage authorized_key file on remote servers

This is a simple tool that I came up after having to on-boarding and off-boarding developers on a
very colourful palette of environments from AWS to 3rd party hosting providers.
This is a simple tool that I came up after having to on-boarding and off-boarding developers on a very colourful palette of environments from AWS to 3rd party hosting providers.

As every one of my creations this tool is solving _my_ problem. It does not warranty your problem will be solved,
but in that highly unlikely event please let me know, fixes and pull requests, issues are all very welcome without
again the promise that I'll do anything, I'm normally really busy, apologies.
As every one of my creations this tool is solving _my_ problem. It does not warranty your problem will be solved, but in that highly unlikely event please let me know, fixes and pull requests, issues are all very welcome without again the promise that I'll do anything, I'm normally really busy, apologies.

**Caution**: Plan your group memberships carefully, keep your management key out of any groups so you don't accidentally remove management key from any server, locking yourself out.

## Installation

Expand Down Expand Up @@ -137,6 +136,19 @@ $ ./sshman rename user oldemail@server.com newemail@server.com
$ ./sshman rename server oldalias newalias
```

### Modifying user and server groupping

Modify user's groups, or remove groups from user to allow global access:
```sh
$ ./sshman groups user email@server.com group1 group2
```

Modify server groups or remove from all groups:
```sh
$ ./sshman groups server serveralias group1 group2
```
Note: Removing server from a group will remove all users that are on the server only because of that group. If the server is in another group, the users that are in both groups will not be removed.

### (Possible) Future Plans

- [x] Reuse stored ssh key for modifying user
Expand Down
12 changes: 11 additions & 1 deletion backend/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ func (c *config) getServers(group string) []Hostentry {
return servers
}

func (c *config) getUsers(group string) []User {
var users []User
for _, user := range c.Users {
if contains(user.Groups, group) {
users = append(users, user)
}
}
return users
}

// AddUserToHosts adds user to all allowed hosts' authorized_keys files
func (c *config) AddUserToHosts(newuser *User) {
for alias, host := range c.Hosts {
Expand Down Expand Up @@ -205,7 +215,7 @@ func (c *config) RegisterUser(oldgroups []string, args ...string) error {
c.Users[lsum] = newuser
log.Printf("Registering %s %s %s %s\n", parts[0], parts[2], args[0], lsum)
c.Write()
return newuser.updateGroups(c, oldgroups, groups)
return newuser.UpdateGroups(c, oldgroups)
}

// UnregisterServer removes a server from the config
Expand Down
9 changes: 4 additions & 5 deletions backend/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,23 +41,22 @@ func contains(slice []string, s string) bool {
return false
}

func updates(oldItems, newItems []string) []string {
var changes []string
func updates(oldItems, newItems []string) (added []string, removed []string) {
ma := make(map[string]struct{}, len(oldItems))
mb := make(map[string]struct{}, len(newItems))
for _, x := range newItems {
mb[x] = struct{}{}
}
for _, x := range oldItems {
if _, found := mb[x]; !found {
changes = append(changes, x)
removed = append(removed, x)
}
ma[x] = struct{}{}
}
for _, x := range newItems {
if _, found := ma[x]; !found {
changes = append(changes, x)
added = append(added, x)
}
}
return changes
return
}
39 changes: 36 additions & 3 deletions backend/hostentry.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,6 @@ func (h *Hostentry) delUser(u *User) error {
log.Printf("Error: Not good line: '%s'\n", line)
}
lsum := checksum(parts[1])
if _, ok := h.Config.Users[lsum]; !ok {
delete(h.Config.Users, lsum)
}
if parts[1] == u.Key {
found = true
continue
Expand All @@ -149,8 +146,44 @@ func (h *Hostentry) delUser(u *User) error {
return err
}
log.Printf("Removed %s from %s\n", u.Email, h.Alias)
} else {
log.Printf("user %s not on %s\n", u.Email, h.Alias)
}
h.Checksum = sum
h.Users = userlist
return nil
}

func (h *Hostentry) UpdateGroups(c *config, oldgroups []string) error {
added, removed := updates(oldgroups, h.Groups)
for _, group := range added {
users := c.getUsers(group)
for _, u := range users {
if !h.hasUser(u.Email) {
log.Printf("Adding %s to %s\n", u.Email, h.Alias)
err := h.addUser(&u)
if err != nil {
return err
}
}
}
}
for _, group := range removed {
users := c.getUsers(group)
for _, u := range users {
if match(u.Groups, h.Groups) {
continue
}
if h.hasUser(u.Email) {
log.Printf("Removing %s from %s\n", u.Email, h.Alias)
err := h.delUser(&u)
if err != nil {
return err
}
}
}
}
c.Hosts[h.Alias] = *h
c.Write()
return nil
}
18 changes: 15 additions & 3 deletions backend/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ type User struct {
Groups []string `json:"groups"`
}

func (u *User) updateGroups(C *config, oldgroups, newgroups []string) error {
changes := updates(oldgroups, newgroups)
for _, group := range changes {
func (u *User) UpdateGroups(C *config, oldgroups []string) error {
added, removed := updates(oldgroups, u.Groups)
for _, group := range added {
servers := C.getServers(group)
for _, server := range servers {
server.readUsers()
Expand All @@ -25,5 +25,17 @@ func (u *User) updateGroups(C *config, oldgroups, newgroups []string) error {
}
}
}
for _, group := range removed {
servers := C.getServers(group)
for _, server := range servers {
server.readUsers()
if server.hasUser(u.Email) {
err := server.delUser(u)
if err != nil {
return err
}
}
}
}
return nil
}
20 changes: 20 additions & 0 deletions cmd/groups.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package cmd

import (
"github.com/spf13/cobra"
)

// groupsCmd represents the groups command
var groupsCmd = &cobra.Command{
Use: "groups",
Short: "Modify user or server groups",
Long: `Modify user's groups, or remove groups from user to allow global access:
$ ./sshman groups user email@server.com group1 group2
Modify server groups or remove from all groups:
$ ./sshman groups server serveralias group1 group2
`,
}

func init() {
rootCmd.AddCommand(groupsCmd)
}
37 changes: 37 additions & 0 deletions cmd/groupsServer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package cmd

import (
"log"

"github.com/shoobyban/sshman/backend"
"github.com/spf13/cobra"
)

// groupsServerCmd represents the user group editing command
var groupsServerCmd = &cobra.Command{
Use: "server",
Short: "Modify group assignments for a server",
Long: `Modify server groups or remove from all groups:
$ ./sshman groups server serveralias group1 group2
`,
Run: func(_ *cobra.Command, args []string) {
cfg := backend.ReadConfig(backend.NewSFTP())
if len(args) < 1 {
return
}
email := args[0]
groups := args[1:]
if host, ok := cfg.Hosts[args[0]]; ok {
oldgroups := host.Groups
host.Groups = groups
cfg.Hosts[args[0]] = host
cfg.Write()
log.Printf("Groups for %s edited: %v\n", email, host.Groups)
host.UpdateGroups(cfg, oldgroups)
}
},
}

func init() {
groupsCmd.AddCommand(groupsServerCmd)
}
38 changes: 38 additions & 0 deletions cmd/groupsUser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package cmd

import (
"log"

"github.com/shoobyban/sshman/backend"
"github.com/spf13/cobra"
)

// groupsUserCmd represents the user group editing command
var groupsUserCmd = &cobra.Command{
Use: "user",
Short: "Modify group assignments of a user",
Long: `Modify user's groups, or remove groups from user to allow global access:
$ ./sshman groups user email@server.com group1 group2
`,
Run: func(_ *cobra.Command, args []string) {
cfg := backend.ReadConfig(backend.NewSFTP())
if len(args) < 1 {
return
}
email := args[0]
groups := args[1:]
key, user := cfg.GetUserByEmail(email)
if user != nil {
oldgroups := user.Groups
user.Groups = groups
cfg.Users[key] = *user
cfg.Write()
log.Printf("Groups for %s edited: %v\n", email, groups)
user.UpdateGroups(cfg, oldgroups)
}
},
}

func init() {
groupsCmd.AddCommand(groupsUserCmd)
}
2 changes: 1 addition & 1 deletion cmd/rename.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"github.com/spf13/cobra"
)

// registerCmd represents the register command
// registerCmd represents the rename command
var renameCmd = &cobra.Command{
Use: "rename",
Short: "Rename a user (modify email) or server (modify alias)",
Expand Down

0 comments on commit c429ab0

Please sign in to comment.