Пример #1
0
func (s *directSessionTokenService) AssumeRole(user *User, role string, enableLDAPRoles bool) (*sts.Credentials, error) {
	var arn string = s.buildARN(role)

	log.Debug("Checking ARN %s against user %s (with access %s)", arn, user.Username, user.ARNs)

	if enableLDAPRoles {
		found := false
		for _, a := range user.ARNs {
			a = s.buildARN(a)
			if arn == a {
				found = true
				break
			}
		}

		log.Debug("Found %s", found)

		if !found {
			return nil, errors.New(fmt.Sprintf("User %s is not authorized to assume role %s!", user.Username, arn))
		}
	}
	options := &sts.AssumeRoleParams{
		DurationSeconds: 3600, // the maximum allowed for AssumeRole
		RoleArn:         arn,
		RoleSessionName: user.Username,
	}

	r, err := s.sts.AssumeRole(options)
	if err != nil {
		return nil, err
	}
	return &r.Credentials, nil
}
Пример #2
0
/*
SSHChallenge performs the challenge-response process to authenticate a connecting client to its SSH keys.
*/
func (sm *server) SSHChallenge(m protocol.MessageReadWriteCloser) (*User, error) {
	for {
		challenge := make([]byte, 64)
		for i := 0; i < len(challenge); i++ {
			challenge[i] = byte(rand.Int() % 256)
		}

		response := &protocol.Message{
			ServerResponse: &protocol.ServerResponse{
				Challenge: &protocol.SSHChallenge{
					Challenge: challenge,
				},
			},
		}

		err := m.Write(response)
		if err != nil {
			return nil, err
		}

		challengeResponseMessage, err := m.Read()
		if err != nil {
			return nil, err
		}

		r := challengeResponseMessage.GetServerRequest()
		if r == nil {
			return nil, errors.New("not a server request")
		}
		cr := r.GetChallengeResponse()
		if cr == nil {
			return nil, errors.New("not a server request")
		}

		// Compose this into the proper format for Authenticate.
		sig := &ssh.Signature{
			Format: cr.GetFormat(),
			Blob:   cr.GetSignature(),
		}
		verifiedUser, err := sm.authenticator.Authenticate("derp", challenge, sig)
		if err != nil {
			return nil, err
		}
		if verifiedUser != nil {
			log.Debug("Verification completed for user %s!", verifiedUser.Username)
			return verifiedUser, nil
		}
		// continue around the loop, letting the client try another key
		verificationFailure := &protocol.Message{
			ServerResponse: &protocol.ServerResponse{
				VerificationFailure: &protocol.SSHVerificationFailure{},
			},
		}
		err = m.Write(verificationFailure)
		if err != nil {
			return nil, err
		}

	}
}
Пример #3
0
/*
Update() searches LDAP for the current user set that supports
the necessary properties for Hologram.

TODO: call this at some point during verification failure so that keys that have
been recently added to LDAP work, instead of requiring a server restart.
*/
func (luc *ldapUserCache) Update() error {
	start := time.Now()
	filter := "(sshPublicKey=*)"
	searchRequest := ldap.NewSearchRequest(
		luc.baseDN,
		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
		0, 0, false,
		filter, []string{"sshPublicKey", luc.userAttr},
		nil,
	)

	searchResult, err := luc.server.Search(searchRequest)
	if err != nil {
		return err
	}

	for _, entry := range searchResult.Entries {
		username := entry.GetAttributeValue(luc.userAttr)
		userKeys := []ssh.PublicKey{}
		for _, eachKey := range entry.GetAttributeValues("sshPublicKey") {
			sshKeyBytes, _ := base64.StdEncoding.DecodeString(eachKey)
			userSSHKey, err := ssh.ParsePublicKey(sshKeyBytes)
			if err != nil {
				userSSHKey, _, _, _, err = ssh.ParseAuthorizedKey([]byte(eachKey))
				if err != nil {
					log.Warning("SSH key parsing for user %s failed (key was '%s')! This key will not be added into LDAP.", username, eachKey)
					continue
				}
			}

			userKeys = append(userKeys, userSSHKey)
		}

		luc.users[username] = &User{
			SSHKeys:  userKeys,
			Username: username,
		}

		log.Debug("Information on %s (re-)generated.", username)
	}

	log.Debug("LDAP information re-cached.")
	luc.stats.Timing(1.0, "ldapCacheUpdate", time.Since(start))
	return nil
}
Пример #4
0
/*
PingHandler returns the correct response for a ping.
*/
func (sm *server) HandlePing(m protocol.MessageReadWriteCloser, p *protocol.Ping) {
	log.Debug("Handling a ping request.")
	sm.stats.Counter(1.0, "messages.ping", 1)

	pingType := protocol.Ping_RESPONSE
	pingMsg := &protocol.Message{
		Ping: &protocol.Ping{
			Type: &pingType,
		},
	}
	m.Write(pingMsg)
}
Пример #5
0
func (luc *ldapUserCache) Authenticate(username string, challenge []byte, sshSig *ssh.Signature) (
	*User, error) {
	// Loop through all of the keys and attempt verification.
	retUser, _ := luc._verify(username, challenge, sshSig)

	if retUser == nil {
		log.Debug("Could not find %s in the LDAP cache; updating from the server.", username)
		luc.stats.Counter(1.0, "ldapCacheMiss", 1)

		// We should update LDAP cache again to retry keys.
		luc.Update()
		return luc._verify(username, challenge, sshSig)
	}
	return retUser, nil
}
Пример #6
0
func request(req *protocol.AgentRequest) (*protocol.AgentResponse, error) {
	client, err := local.NewClient("/var/run/hologram.sock")
	if err != nil {
		return nil, err
	}

	// Try to get to the user's SSH agent, for best compatibility.
	// However, some agents are broken, so we should also try to
	// include the ssh key contents.
	sshAgentSock := os.Getenv("SSH_AUTH_SOCK")
	req.SshAgentSock = &sshAgentSock

	// Send along the raw bytes of the SSH key.
	// TODO(silversupreme): Add in logic for id_dsa, id_ecdsa, etc.
	if sshDir, homeErr := homedir.Expand("~/.ssh"); homeErr == nil {
		sshFilename := fmt.Sprintf("%s/id_rsa", sshDir)
		if sshKeyBytes, keyReadErr := ioutil.ReadFile(sshFilename); keyReadErr == nil {
			req.SshKeyFile = sshKeyBytes
		} else {
			log.Debug("Falling back on DSA key.")
			// Fallback on a user's DSA key if they have one.
			sshFilename := fmt.Sprintf("%s/id_dsa", sshDir)
			if sshKeyBytes, keyReadErr := ioutil.ReadFile(sshFilename); keyReadErr == nil {
				req.SshKeyFile = sshKeyBytes
			}
		}
	}

	msg := &protocol.Message{
		AgentRequest: req,
	}

	err = client.Write(msg)
	if err != nil {
		return nil, err
	}

	response, err := client.Read()

	if response.GetAgentResponse() == nil {
		return nil, fmt.Errorf("Unexpected response type: %v", response)
	}

	return response.GetAgentResponse(), nil
}
Пример #7
0
/*
ConnectionHandler is the root of the state machine created for
each socket that is opened.
*/
func (sm *server) HandleConnection(m protocol.MessageReadWriteCloser) {
	// Loop as long as we have this connection alive.
	log.Debug("Opening new connection handler.")
	for {
		recvMsg, err := m.Read()
		if err != nil {
			// EOFs are normal, so we don't want to report them as errors.
			if err.Error() != "EOF" {
				log.Errorf("Error reading data from stream: %s", err.Error())
			}
			// Right now the behaviour of this is to terminate the connection
			// when we run into an error; should it perhaps send a NAK response
			// and keep the connection open for another retry?
			break
		}

		if pingMsg := recvMsg.GetPing(); pingMsg != nil {
			sm.HandlePing(m, pingMsg)
		} else if reqMsg := recvMsg.GetServerRequest(); reqMsg != nil {
			sm.HandleServerRequest(m, reqMsg)
		}
	}
}
Пример #8
0
/*
HandleServerRequest handles the flow for messages that this server
accepts from clients.
*/
func (sm *server) HandleServerRequest(m protocol.MessageReadWriteCloser, r *protocol.ServerRequest) {
	if assumeRoleMsg := r.GetAssumeRole(); assumeRoleMsg != nil {
		log.Debug("Handling an assumeRole request.")
		sm.stats.Counter(1.0, "messages.assumeRole", 1)

		role := assumeRoleMsg.GetRole()

		user, err := sm.SSHChallenge(m)

		if err != nil {
			log.Errorf("Error trying to handle AssumeRole: %s", err.Error())
			m.Close()
			return
		}

		if user != nil {
			creds, err := sm.credentials.AssumeRole(user, role, sm.enableLDAPRoles)
			if err != nil {
				// error message from Amazon, so forward that on to the client
				errStr := err.Error()
				errMsg := &protocol.Message{
					Error: &errStr,
				}
				log.Errorf("Error from AWS for AssumeRole: %s", err.Error())
				m.Write(errMsg)
				sm.stats.Counter(1.0, "errors.assumeRole", 1)
				//m.Close()
				return
			}
			m.Write(makeCredsResponse(creds))
			return
		}
	} else if getUserCredentialsMsg := r.GetGetUserCredentials(); getUserCredentialsMsg != nil {
		sm.stats.Counter(1.0, "messages.getUserCredentialsMsg", 1)
		user, err := sm.SSHChallenge(m)
		if err != nil {
			log.Errorf("Error trying to handle GetUserCredentials: %s", err.Error())
			m.Close()
			return
		}

		if user != nil {
			creds, err := sm.credentials.AssumeRole(user, sm.DefaultRole, sm.enableLDAPRoles)
			if err != nil {
				log.Errorf("Error trying to handle GetUserCredentials: %s", err.Error())
				m.Close()
				return
			}
			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(
			sm.baseDN,
			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
			fmt.Sprintf("(%s=%s)", sm.userAttr, addSSHKeyMsg.GetUsername()),
			[]string{"sshPublicKey", sm.userAttr, "userPassword"},
			nil)

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

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

		// Check their password.
		password := user.Entries[0].GetAttributeValue("userPassword")
		if password != addSSHKeyMsg.GetPasswordhash() {
			log.Errorf("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.Errorf("Could not modify LDAP user: %s", err.Error())
			return
		}

		successMsg := &protocol.Message{Success: &protocol.Success{}}
		m.Write(successMsg)
		return
	}
}
Пример #9
0
func main() {
	// Parse command-line flags for this system.
	var (
		listenAddress    = flag.String("addr", "", "Address to listen to incoming requests on.")
		ldapAddress      = flag.String("ldapAddr", "", "Address to connect to LDAP.")
		ldapBindDN       = flag.String("ldapBindDN", "", "LDAP DN to bind to for login.")
		ldapInsecure     = flag.Bool("insecureLDAP", false, "INSECURE: Don't use TLS for LDAP connection.")
		ldapBindPassword = flag.String("ldapBindPassword", "", "LDAP password for bind.")
		statsdHost       = flag.String("statsHost", "", "Address to send statsd metrics to.")
		iamAccount       = flag.String("iamaccount", "", "AWS Account ID for generating IAM Role ARNs")
		enableLDAPRoles  = flag.Bool("ldaproles", false, "Enable role support using LDAP directory.")
		roleAttribute    = flag.String("roleattribute", "", "Group attribute to get role from.")
		defaultRole      = flag.String("role", "", "AWS role to assume by default.")
		configFile       = flag.String("conf", "/etc/hologram/server.json", "Config file to load.")
		cacheTimeout     = flag.Int("cachetime", 3600, "Time in seconds after which to refresh LDAP user cache.")
		debugMode        = flag.Bool("debug", false, "Enable debug mode.")
		config           Config
	)

	flag.Parse()

	// Enable debug log output if the user requested it.
	if *debugMode {
		log.DebugMode(true)
		log.Debug("Enabling debug log output. Use sparingly.")
	}

	// Parse in options from the given config file.
	log.Debug("Loading configuration from %s", *configFile)
	configContents, configErr := ioutil.ReadFile(*configFile)
	if configErr != nil {
		log.Errorf("Could not read from config file. The error was: %s", configErr.Error())
		os.Exit(1)
	}

	configParseErr := json.Unmarshal(configContents, &config)
	if configParseErr != nil {
		log.Errorf("Error in parsing config file: %s", configParseErr.Error())
		os.Exit(1)
	}

	// Merge in command flag options.
	if *ldapAddress != "" {
		config.LDAP.Host = *ldapAddress
	}

	if *ldapInsecure {
		config.LDAP.InsecureLDAP = true
	}

	if *ldapBindDN != "" {
		config.LDAP.Bind.DN = *ldapBindDN
	}

	if *ldapBindPassword != "" {
		config.LDAP.Bind.Password = *ldapBindPassword
	}

	if *statsdHost != "" {
		config.Stats = *statsdHost
	}

	if *iamAccount != "" {
		config.AWS.Account = *iamAccount
	}

	if *listenAddress != "" {
		config.Listen = *listenAddress
	}

	if *defaultRole != "" {
		config.AWS.DefaultRole = *defaultRole
	}

	if *enableLDAPRoles {
		config.LDAP.EnableLDAPRoles = true
	}

	if *roleAttribute != "" {
		config.LDAP.RoleAttribute = *roleAttribute
	}

	if *cacheTimeout != 3600 {
		config.CacheTimeout = *cacheTimeout
	}

	var stats g2s.Statter
	var statsErr error

	if config.LDAP.UserAttr == "" {
		config.LDAP.UserAttr = "cn"
	}

	if config.Stats == "" {
		log.Debug("No statsd server specified; no metrics will be emitted by this program.")
		stats = g2s.Noop()
	} else {
		stats, statsErr = g2s.Dial("udp", config.Stats)
		if statsErr != nil {
			log.Errorf("Error connecting to statsd: %s. No metrics will be emitted by this program.", statsErr.Error())
			stats = g2s.Noop()
		} else {
			log.Debug("This program will emit metrics to %s", config.Stats)
		}
	}

	// Setup the server state machine that responds to requests.
	auth, err := aws.GetAuth(os.Getenv("HOLOGRAM_AWSKEY"), os.Getenv("HOLOGRAM_AWSSECRET"), "", time.Now())
	if err != nil {
		log.Errorf("Error getting instance credentials: %s", err.Error())
		os.Exit(1)
	}

	stsConnection := sts.New(auth, aws.Regions["us-east-1"])
	credentialsService := server.NewDirectSessionTokenService(config.AWS.Account, stsConnection)

	var ldapServer *ldap.Conn

	// Connect to the LDAP server using TLS or not depending on the config
	if config.LDAP.InsecureLDAP {
		log.Debug("Connecting to LDAP at server %s (NOT using TLS).", config.LDAP.Host)
		ldapServer, err = ldap.Dial("tcp", config.LDAP.Host)
		if err != nil {
			log.Errorf("Could not dial LDAP! %s", err.Error())
			os.Exit(1)
		}
	} else {
		// Connect to the LDAP server with sample credentials.
		tlsConfig := &tls.Config{
			InsecureSkipVerify: true,
		}

		log.Debug("Connecting to LDAP at server %s.", config.LDAP.Host)
		ldapServer, err = ldap.DialTLS("tcp", config.LDAP.Host, tlsConfig)
		if err != nil {
			log.Errorf("Could not dial LDAP! %s", err.Error())
			os.Exit(1)
		}
	}

	if bindErr := ldapServer.Bind(config.LDAP.Bind.DN, config.LDAP.Bind.Password); bindErr != nil {
		log.Errorf("Could not bind to LDAP! %s", bindErr.Error())
		os.Exit(1)
	}

	ldapCache, err := server.NewLDAPUserCache(ldapServer, stats, config.LDAP.UserAttr, config.LDAP.BaseDN, config.LDAP.EnableLDAPRoles, config.LDAP.RoleAttribute)
	if err != nil {
		log.Errorf("Top-level error in LDAPUserCache layer: %s", err.Error())
		os.Exit(1)
	}

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

	// Wait for a signal from the OS to shutdown.
	terminate := make(chan os.Signal)
	signal.Notify(terminate, syscall.SIGINT, syscall.SIGTERM)

	// SIGUSR1 and SIGUSR2 should make Hologram enable and disable debug logging,
	// respectively.
	debugEnable := make(chan os.Signal)
	debugDisable := make(chan os.Signal)
	signal.Notify(debugEnable, syscall.SIGUSR1)
	signal.Notify(debugDisable, syscall.SIGUSR2)

	// SIGHUP should make Hologram server reload its cache of user information
	// from LDAP.
	reloadCacheSigHup := make(chan os.Signal)
	signal.Notify(reloadCacheSigHup, syscall.SIGHUP)

	// Reload the cache based on time set in configuration
	cacheTimeoutTicker := time.NewTicker(time.Duration(config.CacheTimeout) * time.Second)

	log.Info("Hologram server is online, waiting for termination.")

WaitForTermination:
	for {
		select {
		case <-terminate:
			break WaitForTermination
		case <-debugEnable:
			log.Info("Enabling debug mode.")
			log.DebugMode(true)
		case <-debugDisable:
			log.Info("Disabling debug mode.")
			log.DebugMode(false)
		case <-reloadCacheSigHup:
			log.Info("Force-reloading user cache.")
			ldapCache.Update()
		case <-cacheTimeoutTicker.C:
			log.Info("Cache timeout. Reloading user cache.")
			ldapCache.Update()
		}
	}

	log.Info("Caught signal; shutting down now.")
	server.Close()
}
Пример #10
0
func main() {
	flag.Parse()

	if *debugMode {
		log.DebugMode(true)
		log.Debug("Enabling debug mode. Use sparingly.")
	}

	// Parse in options from the given config file.
	log.Debug("Loading configuration from %s", *configFile)
	configContents, configErr := ioutil.ReadFile(*configFile)
	if configErr != nil {
		log.Errorf("Could not read from config file. The error was: %s", configErr.Error())
		os.Exit(1)
	}

	configParseErr := json.Unmarshal(configContents, &config)
	if configParseErr != nil {
		log.Errorf("Error in parsing config file: %s", configParseErr.Error())
		os.Exit(1)
	}

	// Resolve configuration from the file and commond-line flags.
	// Flags will always take precedence.
	if *dialAddress != "" {
		log.Debug("Using command-line remote address.")
		config.Host = *dialAddress
	}

	// Emit the final config options for debugging if requested.
	log.Debug("Final config:")
	log.Debug("Hologram server address: %s", config.Host)

	defer func() {
		log.Debug("Removing UNIX socket.")
		os.Remove("/var/run/hologram.sock")
	}()

	// Startup the HTTP server and respond to requests.
	listener, err := net.ListenTCP("tcp", &net.TCPAddr{
		IP:   net.ParseIP("169.254.169.254"),
		Port: 80,
	})
	if err != nil {
		log.Errorf("Could not startup the metadata interface: %s", err)
		os.Exit(1)
	}

	credsManager := agent.NewCredentialsExpirationManager()

	mds, metadataError := agent.NewMetadataService(listener, credsManager)
	if metadataError != nil {
		log.Errorf("Could not create metadata service: %s", metadataError.Error())
		os.Exit(1)
	}
	mds.Start()

	// Create a hologram client that can be used by other services to talk to the server
	client := agent.NewClient(config.Host, credsManager)

	agentServer := agent.NewCliHandler("/var/run/hologram.sock", client)
	err = agentServer.Start()
	if err != nil {
		log.Errorf("Could not start agentServer: %s", err.Error())
		os.Exit(1)
	}

	// Wait for a graceful shutdown signal
	terminate := make(chan os.Signal)
	signal.Notify(terminate, syscall.SIGINT, syscall.SIGTERM)

	// SIGUSR1 and SIGUSR2 should make Hologram enable and disable debug logging,
	// respectively.
	debugEnable := make(chan os.Signal)
	debugDisable := make(chan os.Signal)
	signal.Notify(debugEnable, syscall.SIGUSR1)
	signal.Notify(debugDisable, syscall.SIGUSR2)

	log.Info("Hologram agent is online, waiting for termination.")

WaitForTermination:
	for {
		select {
		case <-terminate:
			break WaitForTermination
		case <-debugEnable:
			log.Info("Enabling debug mode.")
			log.DebugMode(true)
		case <-debugDisable:
			log.Info("Disabling debug mode.")
			log.DebugMode(false)
		}
	}

	log.Info("Caught signal; shutting down now.")
}
Пример #11
0
func (h *cliHandler) HandleConnection(c protocol.MessageReadWriteCloser) {
	for {
		msg, err := c.Read()
		if err != nil {
			return
		}
		if msg.GetAgentRequest() != nil {
			dr := msg.GetAgentRequest()

			var (
				sshAgentSock string
				sshKeyBytes  []byte
			)

			sshAgentSock = dr.GetSshAgentSock()
			if sshAgentSock != "" {
				log.Debug("SSH_AUTH_SOCK included in this request: %s", sshAgentSock)
			}

			sshKeyBytes = dr.GetSshKeyFile()
			if sshKeyBytes != nil {
				log.Debug("SSH keyfile included in this request.")
			}

			SSHSetAgentSock(sshAgentSock, sshKeyBytes)

			if dr.GetAssumeRole() != nil {
				log.Debug("Handling AssumeRole request.")
				assumeRole := dr.GetAssumeRole()

				err := h.client.AssumeRole(assumeRole.GetRole())

				var agentResponse protocol.AgentResponse
				if err == nil {
					agentResponse.Success = &protocol.Success{}
				} else {
					log.Errorf(err.Error())
					e := err.Error()
					agentResponse.Failure = &protocol.Failure{
						ErrorMessage: &e,
					}
				}
				msg = &protocol.Message{
					AgentResponse: &agentResponse,
				}
				err = c.Write(msg)
				if err != nil {
					return
				}
			} else if dr.GetGetUserCredentials() != nil {
				log.Debug("Handling GetSessionToken request.")
				err := h.client.GetUserCredentials()

				var agentResponse protocol.AgentResponse
				if err == nil {
					agentResponse.Success = &protocol.Success{}
				} else {
					log.Errorf(err.Error())
					e := err.Error()
					agentResponse.Failure = &protocol.Failure{
						ErrorMessage: &e,
					}
				}
				msg = &protocol.Message{
					AgentResponse: &agentResponse,
				}
				err = c.Write(msg)
				if err != nil {
					return
				}
			} else {
				log.Errorf("Unexpected agent request: %s", dr)
				c.Close()
				return
			}
		} else {
			log.Errorf("Unexpected message: %s", msg)
			c.Close()
			return
		}
	}
}
Пример #12
0
/*
Update() searches LDAP for the current user set that supports
the necessary properties for Hologram.

TODO: call this at some point during verification failure so that keys that have
been recently added to LDAP work, instead of requiring a server restart.
*/
func (luc *ldapUserCache) Update() error {
	start := time.Now()
	if luc.enableLDAPRoles {
		groupSearchRequest := ldap.NewSearchRequest(
			luc.baseDN,
			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
			0, 0, false,
			"(objectClass=groupOfNames)",
			[]string{luc.roleAttribute},
			nil,
		)

		groupSearchResult, err := luc.server.Search(groupSearchRequest)
		if err != nil {
			return err
		}

		for _, entry := range groupSearchResult.Entries {
			dn := entry.DN
			arns := entry.GetAttributeValues(luc.roleAttribute)
			log.Debug("Adding %s to %s", arns, dn)
			luc.groups[dn] = arns
		}
	}

	filter := "(sshPublicKey=*)"
	searchRequest := ldap.NewSearchRequest(
		luc.baseDN,
		ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
		0, 0, false,
		filter, []string{"sshPublicKey", luc.userAttr, "memberOf"},
		nil,
	)

	searchResult, err := luc.server.Search(searchRequest)
	if err != nil {
		return err
	}
	for _, entry := range searchResult.Entries {
		username := entry.GetAttributeValue(luc.userAttr)
		userKeys := []ssh.PublicKey{}
		for _, eachKey := range entry.GetAttributeValues("sshPublicKey") {
			sshKeyBytes, _ := base64.StdEncoding.DecodeString(eachKey)
			userSSHKey, err := ssh.ParsePublicKey(sshKeyBytes)
			if err != nil {
				userSSHKey, _, _, _, err = ssh.ParseAuthorizedKey([]byte(eachKey))
				if err != nil {
					log.Warning("SSH key parsing for user %s failed (key was '%s')! This key will not be added into LDAP.", username, eachKey)
					continue
				}
			}

			userKeys = append(userKeys, userSSHKey)
		}

		arns := []string{}
		if luc.enableLDAPRoles {
			for _, groupDN := range entry.GetAttributeValues("memberOf") {
				log.Debug(groupDN)
				arns = append(arns, luc.groups[groupDN]...)
			}
		}

		luc.users[username] = &User{
			SSHKeys:  userKeys,
			Username: username,
			ARNs:     arns,
		}

		log.Debug("Information on %s (re-)generated.", username)
	}

	log.Debug("LDAP information re-cached.")
	luc.stats.Timing(1.0, "ldapCacheUpdate", time.Since(start))
	return nil
}