func (o *opts) doquery(q query) (*ldap.SearchResult, error) { sr := &ldap.SearchResult{} // parse the ldap URL u, err := url.Parse(q.ldapURL) if err != nil { return sr, err } var port int if u.Scheme == "ldaps" { port = 636 } else if u.Scheme == "ldap" { port = 389 } else { return sr, fmt.Errorf("Unknown LDAP scheme: %s", u.Scheme) } parts := strings.Split(u.Host, ":") hostname := parts[0] if len(parts) > 1 { port, err = strconv.Atoi(parts[1]) if err != nil { return sr, err } } // connect to the ldap server var l *ldap.Conn if u.Scheme == "ldaps" { tlsConfig := tls.Config{} if o.goklp_insecure_skip_verify { tlsConfig.InsecureSkipVerify = true } l, err = ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", hostname, port), &tlsConfig) if err != nil { return sr, err } } else if u.Scheme == "ldap" { l, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", hostname, port)) if err != nil { return sr, err } } defer l.Close() // do an ldap bind err = l.Bind(q.user, q.passwd) if err != nil { return sr, err } // do the ldap search search := ldap.NewSearchRequest( q.baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, q.filter, q.Attributes, nil) sr, err = l.Search(search) if err != nil { return sr, err } return sr, nil }
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() }
func pingLdap(url *url.URL, dial func(proto, addr string) (*ldap.Conn, error)) (err error) { var proto, addr string proto, addr, err = utilUrl.Socket(url.Host) if err != nil { return e.Forward(err) } var conn *ldap.Conn conn, err = dial(proto, addr) if err != nil { return e.Forward(err) } defer func() { conn.Close() }() pass, ok := url.User.Password() if !ok { err = e.New("no password") return } if len(url.Path) > 0 { url.Path = url.Path[1:] } dn := "cn=" + url.User.Username() + "," + url.Path err = conn.Bind(dn, pass) if err != nil { err = e.Forward(err) return } attrs := map[string]bool{ "cn": true, "uid": true, "uidNumber": true, "gidNumber": true, } attrsStr := make([]string, 0, len(attrs)) for val := range attrs { attrsStr = append(attrsStr, val) } search := &ldap.SearchRequest{ BaseDN: basedn(dn), Scope: ldap.ScopeWholeSubtree, DerefAliases: ldap.DerefAlways, Filter: "(&(objectclass=*)(cn=" + url.User.Username() + "))", Attributes: attrsStr, } sr, err := conn.Search(search) if err != nil { err = e.Forward(err) return } if len(sr.Entries) == 1 { entry := sr.Entries[0] if entry.DN == dn { count := 0 for _, attr := range entry.Attributes { if _, found := attrs[attr.Name]; found { count++ } else { return e.New("ldap search returned a no requested attribute") } } if count != len(attrs) { return e.New("wrong number of attributes, required %v, got %v", len(attrs), count) } } } return nil }