How to bind to LDAP server with TLS using golang? - go

I have the following code that works perfectly when binding to an LDAP server without TLS/SSL but when I try to bind to a LDAP server that has TLS setup, it doesn't bind.
/*In order to use this program, the user needs to get the package by running the following command:
go get gopkg.in/ldap.v2*/
package main
import (
"fmt"
"strings"
"gopkg.in/ldap.v2"
"os"
)
//Gives constants to be used for binding to and searching the LDAP server.
const (
ldapServer = "ldaps://test.com:636"
ldapBind = "CN=ad_binder,CN=Users,DC=dev,DC=test,DC=com"
ldapPassword = "Password"
filterDN = "(objectclass=*)"
baseDN = "dc=dev,dc=test,dc=com"
loginUsername = "ad_binder"
loginPassword = "Password"
)
//Main function, which is executed.
func main() {
conn, err := connect()
//If there is an error connecting to server, prints this
if err != nil {
fmt.Printf("Failed to connect. %s", err)
return
}
//Close the connection at a later time.
defer conn.Close()
//Declares err to be list(conn), and checks if any errors. It prints the error(s) if there are any.
if err := list(conn); err != nil {
fmt.Printf("%v", err)
return
}
//Declares err to be auth(conn), and checks if any errors. It prints the error(s) if there are any.
if err := auth(conn); err != nil {
fmt.Printf("%v", err)
return
}
}
//This function is used to connect to the LDAP server.
func connect() (*ldap.Conn, error) {
conn, err := ldap.Dial("tcp", ldapServer)
if err != nil {
return nil, fmt.Errorf("Failed to connect. %s", err)
}
if err := conn.Bind(ldapBind, ldapPassword); err != nil {
return nil, fmt.Errorf("Failed to bind. %s", err)
}
return conn, nil
}
//This function is used to search the LDAP server as well as output the attributes of the entries.
func list(conn *ldap.Conn) error {
//This gets the command line argument and saves it in the form "(argument=*)"
arg := ""
filter := ""
if len(os.Args) > 1{
arg = os.Args[1]
fmt.Println(arg)
filter = "(" + arg + "=*)"
} else{
fmt.Println("You need to input an argument for an attribute to search. I.E. : \"go run anonymous_query.go cn\"")
}
result, err := conn.Search(ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
fmt.Sprintf(filter),
//To add anymore strings to the search, you need to add it here.
[]string{},
nil,
))
if err != nil {
return fmt.Errorf("Failed to search users. %s", err)
}
//Prints all the attributes per entry
for _, entry := range result.Entries {
entry.Print()
fmt.Println()
}
return nil
}
//This function authorizes the user and binds to the LDAP server.
func auth(conn *ldap.Conn) error {
result, err := conn.Search(ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
filter(loginUsername),
[]string{"dn"},
nil,
))
if err != nil {
return fmt.Errorf("Failed to find user. %s", err)
}
if len(result.Entries) < 1 {
return fmt.Errorf("User does not exist")
}
if len(result.Entries) > 1 {
return fmt.Errorf("")
}
if err := conn.Bind(result.Entries[0].DN, loginPassword); err != nil {
fmt.Printf("Failed to auth. %s", err)
} else {
fmt.Printf("Authenticated successfuly!")
}
return nil
}
func filter(needle string) string {
res := strings.Replace(
filterDN,
"{username}",
needle,
-1,
)
return res
}
Here is the error I get when I try to run it:
Failed to connect. Failed to connect. LDAP Result Code 200 "Network Error": dial tcp: address ldaps://test.com:636: too many colons in address
Which is weird since it works with a different LDAP server just fine but with the following:
ldapServer = "10.1.30.47:389"
instead of
ldapServer = "ldaps://test.com:636"
So any help would be greatly appreciated, thanks!

gopkg.in/ldap.v2 does not support URI addressing (i.e. you cannot put the scheme ldap:// or ldaps:// before the network address).
Note: gopkg.in/ldap.v3 does support URI dialing (ldaps://test.com) via DialURL.
If you are using gopkg.in/ldap.v2, you can still establish a direct TLS connection, but you must use the function DialTLS, and use a network address (not a URI):
// addr = "10.1.30.47:389"
// tlsAddr = "10.1.30.47:636"
// conn, err = ldap.Dial("tcp", addr) // non-TLS
// serverName = "test.com" TLS cert name will be verified
// serverName = "" TLS verify will be disabled (DON'T DO THIS IN PRODUCTION)
tlsConf, err := getTLSconfig(serverName)
if err != nil { /* */ }
conn, err = ldap.DialTLS("tcp", tlsAddr, tlsConf)
the above needs a *tls.Config, so use a helper function like:
func getTLSconfig(tlsName string) (tlsC *tls.Config, err error) {
if tlsName != "" {
tlsC = &tls.Config{
ServerName: tlsName,
}
return
}
log.Println("No TLS verification enabled! ***STRONGLY*** recommend adding a trust file to the config.")
tlsC = &tls.Config{
InsecureSkipVerify: true,
}
return
}

Related

SMTP client using a remote SOCKS5/proxy in Go

I am trying to create an SMTP client that uses a proxy connection, SOCKS5.
When I use a local host proxy the code successfully creates an SMTP client.
When I try to use a remote proxy, I am getting a TTL expired. I am also getting an EOF error when trying to use a different proxy connection.
I have set up a proxy server in my localhost, socks5://dante:maluki#127.0.0.1:1080
I have also set up an identical proxy server on my remote VM, socks5://dante:maluki#35.242.186.23:1080
package main
import (
"errors"
"log"
"net"
"net/smtp"
"net/url"
"time"
"golang.org/x/net/idna"
"golang.org/x/net/proxy"
)
const (
smtpTimeout = time.Second * 60
smtpPort = ":25"
)
func init() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
}
func main() {
// The code works when I use a localhost proxy
// socks5://dante:maluki#127.0.0.1:1080
client, err := newSMTPClient("gmail.com", "socks5://dante:maluki#35.242.186.23:1080")
if err != nil {
log.Println(err)
return
}
log.Println(client)
}
// establishProxyConnection connects to the address on the named network address
// via proxy protocol
func establishProxyConnection(addr, proxyURI string) (net.Conn, error) {
// return socks.Dial(proxyURI)("tcp", addr)
u, err := url.Parse(proxyURI)
if err != nil {
log.Println(err)
return nil, err
}
var iface proxy.Dialer
if u.User != nil {
auth := proxy.Auth{}
auth.User = u.User.Username()
auth.Password, _ = u.User.Password()
iface, err = proxy.SOCKS5("tcp", u.Host, &auth, &net.Dialer{Timeout: 30 * time.Second})
if err != nil {
log.Println(err)
return nil, err
}
} else {
iface, err = proxy.SOCKS5("tcp", u.Host, nil, proxy.FromEnvironment())
if err != nil {
log.Println(err)
return nil, err
}
}
dialfunc := iface.Dial
return dialfunc("tcp", addr)
}
// newSMTPClient generates a new available SMTP client
func newSMTPClient(domain, proxyURI string) (*smtp.Client, error) {
domain = domainToASCII(domain)
mxRecords, err := net.LookupMX(domain)
if err != nil {
log.Println(err)
return nil, err
}
if len(mxRecords) == 0 {
return nil, errors.New("No MX records found")
}
// Attempt to connect to SMTP servers
for _, r := range mxRecords {
// Simplified to make the code short
addr := r.Host + smtpPort
c, err := dialSMTP(addr, proxyURI)
if err != nil {
log.Println(err)
continue
}
return c, err
}
return nil, errors.New("failed to created smtp.Client")
}
// dialSMTP is a timeout wrapper for smtp.Dial. It attempts to dial an
// SMTP server (socks5 proxy supported) and fails with a timeout if timeout is reached while
// attempting to establish a new connection
func dialSMTP(addr, proxyURI string) (*smtp.Client, error) {
// Channel holding the new smtp.Client or error
ch := make(chan interface{}, 1)
// Dial the new smtp connection
go func() {
var conn net.Conn
var err error
conn, err = establishProxyConnection(addr, proxyURI)
if err != nil {
log.Println(err)
}
if err != nil {
ch <- err
return
}
host, _, err := net.SplitHostPort(addr)
if err != nil {
log.Println(err)
}
client, err := smtp.NewClient(conn, host)
log.Println(client)
if err != nil {
log.Println(err)
ch <- err
return
}
ch <- client
}()
// Retrieve the smtp client from our client channel or timeout
select {
case res := <-ch:
switch r := res.(type) {
case *smtp.Client:
return r, nil
case error:
return nil, r
default:
return nil, errors.New("Unexpected response dialing SMTP server")
}
case <-time.After(smtpTimeout):
return nil, errors.New("Timeout connecting to mail-exchanger")
}
}
// domainToASCII converts any internationalized domain names to ASCII
// reference: https://en.wikipedia.org/wiki/Punycode
func domainToASCII(domain string) string {
asciiDomain, err := idna.ToASCII(domain)
if err != nil {
return domain
}
return asciiDomain
}

Pgxpool returns "pool closed" error on Scan

I'm trying to implement pgxpool in a new go app. I keep getting a "pool closed" error after attempting a scan into a struct.
The pgx logger into gives me this after connecting. I thought the pgxpool was meant to remain open.
{"level":"info","msg":"closed connection","pid":5499,"time":"2022-02-24T16:36:33+10:30"}
Here is my router code
func router() http.Handler {
var err error
config, err := pgxpool.ParseConfig(os.Getenv("DATABASE_URL"))
if err != nil {
log.Fatalln(err)
}
log.Println(os.Getenv("DATABASE_URL"))
logrusLogger := &logrus.Logger{
Out: os.Stderr,
Formatter: new(logrus.JSONFormatter),
Hooks: make(logrus.LevelHooks),
Level: logrus.InfoLevel,
ExitFunc: os.Exit,
ReportCaller: false,
}
config.ConnConfig.Logger = NewLogger(logrusLogger)
db, err := pgxpool.ConnectConfig(context.Background(), config)
if err != nil {
log.Fatalln(err)
}
defer db.Close()
--- minio connection
rs := newAppResource(db, mc)
Then, in a helper file I setup the resource
type appResource struct {
db *pgxpool.Pool
mc *minio.Client
}
// newAppResource function to pass global var
func newAppResource(db *pgxpool.Pool, mc *minio.Client) *appResource {
return &appResource{
db: db,
mc: mc,
}
}
There "pool closed" error occurs at the end of this code
func (rs *appResource) login(w http.ResponseWriter, r *http.Request) {
var user User
var login Login
d := json.NewDecoder(r.Body)
d.DisallowUnknownFields() // catch unwanted fields
err := d.Decode(&login)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if err != nil {
fmt.Println("can't decode JSON", err)
}
if login.Email == "" {
log.Println("empty email")
return
}
log.Println(login.Email)
log.Println(login.Password)
if login.Password == "" {
log.Println("empty password")
return
}
// optional extra check
if d.More() {
http.Error(w, "extraneous data after JSON object", http.StatusBadRequest)
return
}
sqlStatement := "SELECT user_id, password FROM users WHERE active = 'true' AND email = ?"
row := rs.db.QueryRow(context.Background(), sqlStatement, login.Email)
err = row.Scan(&user.UserId, &user.Password)
if err == sql.ErrNoRows {
log.Println("user not found")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
if err != nil {
log.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
It appears that you are doing something like the following:
func router() http.Handler {
db, err := pgxpool.ConnectConfig(context.Background(), config)
if err != nil {
log.Fatalln(err)
}
defer db.Close()
return appResource{db: db}
}
The issue with this is that the defer db.Close() runs when the function router() ends and this is before the returned pgxPool.Pool is actually used (the http.Handler returned will be used later when http requests are processed). Attempting to use a closed pgxPool.Pool results in the error you are seeing.
The simplest solution is to simply remove the defer db.Close() however you might also consider calling db.Close() as part of a clean shutdown process (it needs to remain open as long as you are handling requests).
You are using pgxpool which does differ from the standard library; however I believe that the advice given in the standard library docs applies here:
It is rarely necessary to close a DB.

ssh server in go : how to offer public key types different than rsa?

I’m trying to create a ssh server in go using the x/crypto/ssh module but i can’t manage to make the public key authentification work.
I tried the ExampleNewServerConn() function in the ssh/example_test.go file (in the https://go.googlesource.com/crypto repo) but the public key method doesn’t work, it looks like the server isn’t advertising the right algorithms because i get this line when trying to connect with a ssh client :
debug1: send_pubkey_test: no mutual signature algorithm
If i add -o PubkeyAcceptedKeyTypes=+ssh-rsa the public key login works, but this rsa method is deprecated, i would like to use another public key type, how can i do that ?
Thanks in advance.
Edit : here is the code that i used to test
package main
import (
"fmt"
"io/ioutil"
"log"
"net"
"golang.org/x/crypto/ssh"
terminal "golang.org/x/term"
)
func main() {
authorizedKeysBytes, err := ioutil.ReadFile("authorized_keys")
if err != nil {
log.Fatalf("Failed to load authorized_keys, err: %v", err)
}
authorizedKeysMap := map[string]bool{}
for len(authorizedKeysBytes) > 0 {
pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
if err != nil {
log.Fatal(err)
}
authorizedKeysMap[string(pubKey.Marshal())] = true
authorizedKeysBytes = rest
}
config := &ssh.ServerConfig{
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
if c.User() == "testuser" && string(pass) == "tiger" {
return nil, nil
}
return nil, fmt.Errorf("password rejected for %q", c.User())
},
PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
if authorizedKeysMap[string(pubKey.Marshal())] {
return &ssh.Permissions{
// Record the public key used for authentication.
Extensions: map[string]string{
"pubkey-fp": ssh.FingerprintSHA256(pubKey),
},
}, nil
}
return nil, fmt.Errorf("unknown public key for %q", c.User())
},
}
privateBytes, err := ioutil.ReadFile("id_rsa")
if err != nil {
log.Fatal("Failed to load private key: ", err)
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
log.Fatal("Failed to parse private key: ", err)
}
config.AddHostKey(private)
listener, err := net.Listen("tcp", "0.0.0.0:2022")
if err != nil {
log.Fatal("failed to listen for connection: ", err)
}
nConn, err := listener.Accept()
if err != nil {
log.Fatal("failed to accept incoming connection: ", err)
}
conn, chans, reqs, err := ssh.NewServerConn(nConn, config)
if err != nil {
log.Fatal("failed to handshake: ", err)
}
log.Printf("logged in with key %s", conn.Permissions.Extensions["pubkey-fp"])
go ssh.DiscardRequests(reqs)
for newChannel := range chans {
if newChannel.ChannelType() != "session" {
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
continue
}
channel, requests, err := newChannel.Accept()
if err != nil {
log.Fatalf("Could not accept channel: %v", err)
}
go func(in <-chan *ssh.Request) {
for req := range in {
req.Reply(req.Type == "shell", nil)
}
}(requests)
term := terminal.NewTerminal(channel, "> ")
go func() {
defer channel.Close()
for {
line, err := term.ReadLine()
if err != nil {
break
}
fmt.Println(line)
}
}()
}
}
I found why the client and the server can’t communicate, the rsa-sha2 algorithms are not yet implemented in the x/crypto library. There is an issue about it on github : https://github.com/golang/go/issues/49952 .
A temporary solution is to add
replace golang.org/x/crypto => github.com/rmohr/crypto v0.0.0-20211203105847-e4ed9664ac54
at the end of your go.mod file, it uses a x/crypto fork from #rmohr that works with rsa-sha2.
This is the easy way to do it, let letsencrypt handle the certificates for you :)
func main() {
r := mux.NewRouter()
r.HandleFunc("/index", index)
certManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist("www.example.com"), // replace with your domain
Cache: autocert.DirCache("certs"),
}
srv := &http.Server{
Handler: r,
Addr: ":https",
WriteTimeout: 5 * time.Second,
ReadTimeout: 5 * time.Second,
TLSConfig: &tls.Config{
GetCertificate: certManager.GetCertificate,
},
}
go http.ListenAndServe(":http", certManager.HTTPHandler(nil)) //nolint
log.Fatal(srv.ListenAndServeTLS("", ""))
}

Getting error "failed to send packet header: EOF" while uploading file to sftp server

I am facing an issue where in, whenever I try to upload a file to a remote sftp server, I get an error saying "failed to send packet header: EOF". This occurs when I try to perform the uploading step from my own hosted EC2 instance. While locally, everything works fine.
Sftp client is initiated as follow.
// Connect to server
var authMethods []ssh.AuthMethod
// Use password authentication if password provided
if pass != "" {
authMethods = append(authMethods, ssh.Password(pass))
}
config := ssh.ClientConfig{
User: user,
Auth: authMethods,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
conn, err := ssh.Dial("tcp", addr, &config)
if err != nil {
return nil, tearDown, errors.Wrap(err, fmt.Sprintf("failed to connect to %s", addr))
}
tearDown = func() {
_ = conn.Close()
}
// Create new SFTP client
sc, err := sftp.NewClient(conn)
if err != nil {
return nil, tearDown, errors.Wrap(err, "Unable to start SFTP subsystem")
}
tearDown = func() {
fmt.Println("defer is called. closing connection now .... ")
_ = conn.Close()
_ = sc.Close()
}
return sc, tearDown, nil
And instance of sc is attached to a struct and passed around the codebase
Function invoked while uploading file is as follow.
file, err := s.sc.OpenFile(remoteFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
defer func() {
if file == nil {
return
}
cErr := file.Close()
if cErr != nil {
fmt.Println(fmt.Sprintf("error while closing file %v", cErr))
}
}()
if err != nil {
fmt.Println(fmt.Sprintf("error while opening file %v", err))
return err
}
_, err = file.Write(data)
if err != nil {
fmt.Println(fmt.Sprintf("error while writing to file %v", err))
return err
}
return nil
Can someone guide me as in where is the error coming from?

How to retrieve all attributes of an LDAP entry with golang query?

I'm using the gopkg.in/ldap.v2 api to query an LDAP server, but was wondering how to retrieve all the attributes of an entry once it shows up in the query result? Here is the full program I have:
/*In order to use this program, the user needs to get the package by running the following command:
go get gopkg.in/ldap.v2*/
package main
import (
"fmt"
"strings"
"gopkg.in/ldap.v2"
"os"
)
//Gives constants to be used for binding to and searching the LDAP server.
const (
ldapServer = "127.0.0.1:389"
ldapBind = "cn=admin,dc=test123,dc=com"
ldapPassword = "Password"
filterDN = "(objectclass=*)"
baseDN = "dc=test123,dc=com"
loginUsername = "admin"
loginPassword = "Password"
)
//Main function, which is executed.
func main() {
conn, err := connect()
//If there is an error connecting to server, prints this
if err != nil {
fmt.Printf("Failed to connect. %s", err)
return
}
//Close the connection at a later time.
defer conn.Close()
//Declares err to be list(conn), and checks if any errors. It prints the error(s) if there are any.
if err := list(conn); err != nil {
fmt.Printf("%v", err)
return
}
/*
//Declares err to be auth(conn), and checks if any errors. It prints the error(s) if there are any.
if err := auth(conn); err != nil {
fmt.Printf("%v", err)
return
}*/
}
//This function is used to connect to the LDAP server.
func connect() (*ldap.Conn, error) {
conn, err := ldap.Dial("tcp", ldapServer)
if err != nil {
return nil, fmt.Errorf("Failed to connect. %s", err)
}
if err := conn.Bind(ldapBind, ldapPassword); err != nil {
return nil, fmt.Errorf("Failed to bind. %s", err)
}
return conn, nil
}
//This function is used to search the LDAP server as well as output the attributes of the entries.
func list(conn *ldap.Conn) error {
//This gets the command line argument and saves it in the form "(argument=*)"
arg := ""
filter := ""
if len(os.Args) > 1{
arg = os.Args[1]
fmt.Println(arg)
filter = "(" + arg + "=*)"
} else{
fmt.Println("You need to input an argument for an attribute to search. I.E. : \"go run anonymous_query.go cn\"")
}
result, err := conn.Search(ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
fmt.Sprintf(filter),
//To add anymore strings to the search, you need to add it here.
[]string{"dn", "o", "cn", "ou", "uidNumber", "objectClass",
"uid", "uidNumber", "gidNumber", "homeDirectory", "loginShell", "gecos",
"shadowMax", "shadowWarning", "shadowLastChange", "dc", "description", "entryCSN"},
nil,
))
if err != nil {
return fmt.Errorf("Failed to search users. %s", err)
}
//Prints all the attributes per entry
for _, entry := range result.Entries {
entry.Print()
fmt.Println()
}
return nil
}
//This function authorizes the user and binds to the LDAP server.
func auth(conn *ldap.Conn) error {
result, err := conn.Search(ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
filter(loginUsername),
[]string{"dn"},
nil,
))
if err != nil {
return fmt.Errorf("Failed to find user. %s", err)
}
if len(result.Entries) < 1 {
return fmt.Errorf("User does not exist")
}
if len(result.Entries) > 1 {
return fmt.Errorf("")
}
if err := conn.Bind(result.Entries[0].DN, loginPassword); err != nil {
fmt.Printf("Failed to auth. %s", err)
} else {
fmt.Printf("Authenticated successfuly!")
}
return nil
}
func filter(needle string) string {
res := strings.Replace(
filterDN,
"{username}",
needle,
-1,
)
return res
}
The issue I have is in this line:
//To add anymore strings to the search, you need to add it here.
[]string{"dn", "o", "cn", "ou", "uidNumber", "objectClass",
"uid", "uidNumber", "gidNumber", "homeDirectory", "loginShell", "gecos",
"shadowMax", "shadowWarning", "shadowLastChange", "dc", "description", "entryCSN"}
I would want to retrieve all the attributes of an LDAP entry rather than having to manually type all the attributes that I want from the query result. Another reason is because I don't know what attributes an entry may have.
Any help would be greatly appreciated. Thanks!
In the LDAP search operation, if you don't specify attributes for the search it will return the entries with all their attributes, so this will do the job:
result, err := conn.Search(ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
fmt.Sprintf(filter),
[]string{},
nil,
))

Resources