Skip to content

Commit

Permalink
Add a program that can add a user's SSH key.
Browse files Browse the repository at this point in the history
This should be run automatically during dev-environment setup.
  • Loading branch information
gaylatea committed Feb 11, 2015
1 parent 95ee516 commit 834dbef
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 8 deletions.
16 changes: 11 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ setup: .setup-complete

package: bin/darwin/Hologram-$(GIT_TAG).pkg bin/linux/hologram-$(GIT_TAG).deb bin/linux/hologram-server-$(GIT_TAG).deb

build: bin/darwin/hologram-server bin/linux/hologram-server bin/darwin/hologram-agent bin/linux/hologram-agent bin/darwin/hologram-cli bin/linux/hologram-cli
build: bin/darwin/hologram-server bin/linux/hologram-server bin/darwin/hologram-agent bin/linux/hologram-agent bin/darwin/hologram-cli bin/linux/hologram-cli bin/darwin/hologram-authorize bin/linux/hologram-authorize

protocol/hologram.pb.go: protocol/hologram.proto
protoc --go_out=. protocol/hologram.proto
Expand All @@ -52,6 +52,10 @@ bin/%/hologram-server: protocol/hologram.pb.go server/.deps server/*.go server/*
@echo "Building server version $(GIT_TAG)$(GIT_DIRTY)"
@cd server/bin; gox -osarch="$*/amd64" -output="../../bin/$*/hologram-server"

bin/%/hologram-authorize: protocol/hologram.pb.go tools/install/*.go log/*.go log/.deps transport/remote/*.go transport/remote/bindata.go
@echo "Building SSH key updater version $(GIT_TAG)$(GIT_DIRTY)"
@cd tools/install; gox -osarch="$*/amd64" -output="../../bin/$*/hologram-authorize"

bin/%/hologram-cli: protocol/hologram.pb.go cli/*/*.go log/*.go log/.deps transport/local/*.go cli/.deps
@echo "Building CLI version $(GIT_TAG)$(GIT_DIRTY)"
@cd cli/bin; gox -osarch="$*/amd64" -output="../../bin/$*/hologram-cli"
Expand All @@ -60,12 +64,13 @@ bin/ping: tools/ping/main.go log/*.go log/.deps
@cd tools/ping; go build
@mv tools/ping/ping bin/ping

bin/darwin/Hologram-%.pkg: bin/darwin/hologram-agent bin/darwin/hologram-cli agent/support/darwin/com.adroll.hologram*.plist agent/support/darwin/postinstall.sh
bin/darwin/Hologram-%.pkg: bin/darwin/hologram-agent bin/darwin/hologram-cli bin/darwin/hologram-authorize agent/support/darwin/com.adroll.hologram*.plist agent/support/darwin/postinstall.sh
@echo "Creating temporary directory for pkgbuild..."
@mkdir -p pkg/darwin/{root,scripts}
@mkdir -p ./pkg/darwin/root/{usr/bin,Library/{LaunchDaemons,LaunchAgents},etc/hologram}
@cp ./bin/darwin/hologram-agent ./pkg/darwin/root/usr/bin/hologram-agent
@cp ./bin/darwin/hologram-cli ./pkg/darwin/root/usr/bin/hologram
@cp ./bin/darwin/hologram-authorize ./pkg/darwin/root/usr/bin/hologram-authorize
@cp ./config/agent.json ./pkg/darwin/root/etc/hologram/agent.json
@cp ./agent/support/darwin/hologram-boot.sh ./pkg/darwin/root/usr/bin/hologram-boot
@cp ./agent/support/darwin/com.adroll.hologram-ip.plist ./pkg/darwin/root/Library/LaunchDaemons
Expand Down Expand Up @@ -104,13 +109,14 @@ bin/linux/hologram-server-%.deb: bin/linux/hologram-server server/after-install.
-a amd64 \
./

bin/linux/hologram-%.deb: bin/linux/hologram-cli bin/linux/hologram-agent
bin/linux/hologram-%.deb: bin/linux/hologram-cli bin/linux/hologram-agent bin/linux/hologram-authorize
@echo "Creating temporary directory for fpm..."
@mkdir -p ./pkg/linux/hologram/{root,scripts}
@mkdir -p ./pkg/linux/hologram/root/{usr/local/bin,etc/{hologram,init.d}}
@cp ./config/agent.json ./pkg/linux/hologram/root/etc/hologram/agent.json
@cp ./bin/linux/hologram-cli ./pkg/linux/hologram/root/usr/local/bin/hologram
@cp ./bin/linux/hologram-agent ./pkg/linux/hologram/root/usr/local/bin/hologram-agent
@cp ./bin/linux/hologram-authorize ./pkg/linux/hologram/root/usr/local/bin/hologram-authorize
@cp ./agent/support/debian/after-install.sh ./pkg/linux/hologram/scripts/
@cp ./agent/support/debian/before-remove.sh ./pkg/linux/hologram/scripts/
@cp ./agent/support/debian/init.sh ./pkg/linux/hologram/root/etc/init.d/hologram-agent
Expand All @@ -132,10 +138,10 @@ test: protocol/hologram.pb.go server/.deps agent/.deps transport/remote/bindata.
clean:
rm -rf ./bin ./build
sudo rm -rf ./pkg

version:
@echo "$(GIT_TAG)"

.PHONY: setup all build package clean test version
.PHONY: setup all build package clean test version


9 changes: 9 additions & 0 deletions protocol/hologram.proto
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ message Message {
ServerResponse serverResponse = 7;
AgentRequest agentRequest = 8;
AgentResponse agentResponse = 9;
Success success = 10;
Failure failure = 11;
}
}

Expand All @@ -49,6 +51,7 @@ message ServerRequest {
SSHChallengeResponse challengeResponse = 5;
MFATokenResponse tokenResponse = 6;
GetUserCredentials getUserCredentials = 7;
AddSSHKey addSSHkey = 8;
}
}

Expand All @@ -59,6 +62,12 @@ message AssumeRole {

message GetUserCredentials {}

message AddSSHKey {
required string username = 1;
required string passwordhash = 2;
required string sshkeybytes = 3;
}

message SSHChallengeResponse {
required bytes signature = 1;
required string format = 2;
Expand Down
2 changes: 1 addition & 1 deletion server/bin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func main() {
os.Exit(1)
}

serverHandler := server.New(ldapCache, credentialsService, config.AWS.DefaultRole, stats)
serverHandler := server.New(ldapCache, credentialsService, config.AWS.DefaultRole, stats, ldapServer)
server, err := remote.NewServer(config.Listen, serverHandler.HandleConnection)

// Wait for a signal from the OS to shutdown.
Expand Down
56 changes: 55 additions & 1 deletion server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ package server

import (
"errors"
"fmt"
"github.com/AdRoll/hologram/log"
"github.com/AdRoll/hologram/protocol"
"github.com/goamz/goamz/sts"
"github.com/nmcclain/ldap"
"github.com/peterbourgon/g2s"
"golang.org/x/crypto/ssh"
"math/rand"
Expand All @@ -38,6 +40,7 @@ type server struct {
credentials CredentialService
stats g2s.Statter
DefaultRole string
ldapServer LDAPImplementation
}

/*
Expand Down Expand Up @@ -139,6 +142,56 @@ func (sm *server) HandleServerRequest(m protocol.MessageReadWriteCloser, r *prot
m.Write(makeCredsResponse(creds))
return
}
} else if addSSHKeyMsg := r.GetAddSSHkey(); addSSHKeyMsg != nil {
sm.stats.Counter(1.0, "messages.addSSHKeyMsg", 1)

// Search for the user specified in this request.
sr := ldap.NewSearchRequest(
"dc=keeponprovoking,dc=com",
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(cn=%s)", addSSHKeyMsg.GetUsername()),
[]string{"sshPublicKey", "cn", "userPassword"},
nil)

user, err := sm.ldapServer.Search(sr)
if err != nil {
log.Error("Error trying to handle addSSHKeyMsg: %s", err.Error())
return
}

if len(user.Entries) == 0 {
log.Error("User %s not found!", addSSHKeyMsg.GetUsername())
return
}

// Check their password.
password := user.Entries[0].GetAttributeValue("userPassword")
if password != addSSHKeyMsg.GetPasswordhash() {
log.Error("Provided password for user %s does not match %s!", addSSHKeyMsg.GetUsername(), password)
return
}

// Check to see if this SSH key already exists.
for _, k := range user.Entries[0].GetAttributeValues("sshPublicKey") {
if k == addSSHKeyMsg.GetSshkeybytes() {
log.Warning("User %s already has this SSH key. Doing nothing.", addSSHKeyMsg.GetUsername())
successMsg := &protocol.Message{Success: &protocol.Success{}}
m.Write(successMsg)
return
}
}

mr := ldap.NewModifyRequest(user.Entries[0].DN)
mr.Add("sshPublicKey", []string{addSSHKeyMsg.GetSshkeybytes()})
err = sm.ldapServer.Modify(mr)
if err != nil {
log.Error("Could not modify LDAP user: %s", err.Error())
return
}

successMsg := &protocol.Message{Success: &protocol.Success{}}
m.Write(successMsg)
return
}
}

Expand Down Expand Up @@ -225,11 +278,12 @@ func makeCredsResponse(creds *sts.Credentials) *protocol.Message {
New returns a server that can be used as a handler for a
MessageConnection loop.
*/
func New(a Authenticator, c CredentialService, r string, s g2s.Statter) *server {
func New(a Authenticator, c CredentialService, r string, s g2s.Statter, l LDAPImplementation) *server {
return &server{
credentials: c,
authenticator: a,
stats: s,
DefaultRole: r,
ldapServer: l,
}
}
87 changes: 86 additions & 1 deletion server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ import (
"github.com/AdRoll/hologram/protocol"
"github.com/AdRoll/hologram/server"
"github.com/goamz/goamz/sts"
"github.com/nmcclain/ldap"
"github.com/peterbourgon/g2s"
. "github.com/smartystreets/goconvey/convey"
"golang.org/x/crypto/ssh"
"io"
"reflect"
"testing"
"time"
)
Expand Down Expand Up @@ -76,10 +78,57 @@ func (*dummyCredentials) AssumeRole(user *server.User, role string) (*sts.Creden
}, nil
}

type DummyLDAP struct {
username string
password string
sshKeys []string
req *ldap.ModifyRequest
}

func (l *DummyLDAP) Search(*ldap.SearchRequest) (*ldap.SearchResult, error) {
return &ldap.SearchResult{
Entries: []*ldap.Entry{
&ldap.Entry{DN: "something",
Attributes: []*ldap.EntryAttribute{
&ldap.EntryAttribute{
Name: "cn",
Values: []string{l.username},
},
&ldap.EntryAttribute{
Name: "userPassword",
Values: []string{l.password},
},
&ldap.EntryAttribute{
Name: "sshPublicKey",
Values: l.sshKeys,
},
},
},
},
}, nil
}

func (l *DummyLDAP) Modify(mr *ldap.ModifyRequest) error {
if reflect.DeepEqual(mr, l.req) {
l.sshKeys = []string{"test"}
}
return nil
}

func TestServerStateMachine(t *testing.T) {
// This silly thing is needed for equality testing for the LDAP dummy.
neededModifyRequest := ldap.NewModifyRequest("something")
neededModifyRequest.Add("sshPublicKey", []string{"test"})

Convey("Given a state machine setup with a null logger", t, func() {
authenticator := &DummyAuthenticator{&server.User{Username: "words"}}
testServer := server.New(authenticator, &dummyCredentials{}, "default", g2s.Noop())
ldap := &DummyLDAP{
username: "ari.adair",
password: "098f6bcd4621d373cade4e832627b4f6",
sshKeys: []string{},
req: neededModifyRequest,
}
testServer := server.New(authenticator, &dummyCredentials{}, "default", g2s.Noop(), ldap)
r, w := io.Pipe()

testConnection := protocol.NewMessageConnection(ReadWriter(r, w))
Expand Down Expand Up @@ -180,5 +229,41 @@ func TestServerStateMachine(t *testing.T) {
So(credsMsg.GetServerResponse().GetVerificationFailure(), ShouldNotBeNil)
})
})

Convey("When a request to add an SSH key comes in", func() {
user := "ari.adair"
password := "098f6bcd4621d373cade4e832627b4f6"
sshKey := "test"
testMessage := &protocol.Message{
ServerRequest: &protocol.ServerRequest{
AddSSHkey: &protocol.AddSSHKey{
Username: &user,
Passwordhash: &password,
Sshkeybytes: &sshKey,
},
},
}

testConnection.Write(testMessage)
Convey("If this request is valid", func() {
msg, err := testConnection.Read()
if err != nil {
t.Fatal(err)
}

if msg.GetSuccess() == nil {
t.Fail()
}
Convey("It should add the SSH key to the user.", func() {
So(ldap.sshKeys[0], ShouldEqual, sshKey)
Convey("If the user tries to add the same SSH key", func() {
testConnection.Write(testMessage)
Convey("It should not insert the same key twice.", func() {
So(len(ldap.sshKeys), ShouldEqual, 1)
})
})
})
})
})
})
}
1 change: 1 addition & 0 deletions server/usercache.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ This interface exists for testing purposes.
*/
type LDAPImplementation interface {
Search(*ldap.SearchRequest) (*ldap.SearchResult, error)
Modify(*ldap.ModifyRequest) error
}

/*
Expand Down
4 changes: 4 additions & 0 deletions server/usercache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ func (sls *StubLDAPServer) Search(s *ldap.SearchRequest) (*ldap.SearchResult, er
}, nil
}

func (*StubLDAPServer) Modify(*ldap.ModifyRequest) error {
return nil
}

func randomBytes(length int) []byte {
buf := make([]byte, length)

Expand Down
Loading

0 comments on commit 834dbef

Please sign in to comment.