initial work on adding LDAP integration
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -29,7 +29,9 @@ Written by Nathan Coad (nathan.coad@dell.com)
|
||||
| LOG_FILE | Specify the name/path of file to write log messages to | /var/log/smt.log | ./smt.log
|
||||
| BIND_IP | Specify the local IP address to bind to. | 127.0.0.1 | Primary IPv4 address |
|
||||
| BIND_PORT | Specify the TCP/IP port to bind to. | 443 | 8443 |
|
||||
| LDAP_BIND_ADDRESS | If LDAP integration is needed, specify the LDAP Bind address | ldaps://dc.domain.com:636 | No default specified |
|
||||
| LDAP_BIND_ADDRESS | If LDAP integration is needed, specify the LDAP Bind address. Only LDAPS on port 636 is supported. Do not specify port 636 in the bind address | ldaps://dc.example.com | No default specified |
|
||||
| LDAP_BASE_DN | If LDAP integration is needed, specify the base DN to use when binding to AD | "OU=Users,DC=example,DC=com" | No default specified |
|
||||
| LDAP_TRUST_CERT_FILE | If LDAP integration is needed, specify filepath to PEM format public certificate of Certificate Authority signing LDAPS communications | caroot.pem | No default specified, must define this value |
|
||||
| TLS_KEY_FILE | Specify the filename of the TLS certificate private key (must be unencrypted) in PEM format | key.pem | privkey.pem |
|
||||
| TLS_CERT_FILE | Specify the filename of the TLS certificate file in PEM format | cert.pem | cert.pem |
|
||||
| TOKEN_HOUR_LIFESPAN | Number of hours that the JWT token returned at login is valid | 12 | No default specified, must define this value |
|
||||
|
@@ -59,7 +59,7 @@ func StoreSecret(c *gin.Context) {
|
||||
|
||||
if len(checkExists) > 0 {
|
||||
log.Printf("StoreSecret not storing secret with '%d' already matching secrets.\n", len(checkExists))
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "StoreSecret attempting to store secret already defined. API calls for update/delete don't yet exist"})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "StoreSecret attempting to store secret already defined. Use update API call instead"})
|
||||
return
|
||||
}
|
||||
|
||||
|
12
go.mod
12
go.mod
@@ -5,9 +5,10 @@ go 1.19
|
||||
require (
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/gin-gonic/gin v1.9.0
|
||||
github.com/go-ldap/ldap v3.0.3+incompatible
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/joho/godotenv v1.5.1
|
||||
golang.org/x/crypto v0.7.0
|
||||
golang.org/x/crypto v0.13.0
|
||||
modernc.org/sqlite v1.21.0
|
||||
)
|
||||
|
||||
@@ -20,7 +21,7 @@ require (
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.12.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||
@@ -34,11 +35,12 @@ require (
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
|
25
go.sum
25
go.sum
@@ -1,3 +1,5 @@
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.8.6 h1:aUgO9S8gvdN6SyW2EhIpAw5E4ChworywIEndZCkCVXk=
|
||||
github.com/bytedance/sonic v1.8.6/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
@@ -15,6 +17,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
|
||||
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
|
||||
github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk=
|
||||
github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
@@ -31,8 +35,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
@@ -82,24 +86,27 @@ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
5
main.go
5
main.go
@@ -147,6 +147,9 @@ func main() {
|
||||
models.ReceiveKey(keyString)
|
||||
}
|
||||
|
||||
// Load certificate for LDAP connectivy
|
||||
models.LoadLdapCert()
|
||||
|
||||
// Create context that listens for the interrupt signal from the OS.
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
@@ -265,6 +268,8 @@ func main() {
|
||||
protected.POST("/retrieveMultiple", controllers.RetrieveMultpleSecrets)
|
||||
protected.POST("/store", controllers.StoreSecret)
|
||||
protected.POST("/update", controllers.UpdateSecret)
|
||||
// TODO
|
||||
//protected.POST("/delete", controllers.DeleteSecret)
|
||||
|
||||
// Support parameters in path
|
||||
// See https://gin-gonic.com/docs/examples/param-in-path/
|
||||
|
146
models/ldap.go
Normal file
146
models/ldap.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/go-ldap/ldap"
|
||||
)
|
||||
|
||||
// Code relating to AD integration
|
||||
|
||||
type LdapConfig struct {
|
||||
LdapBindAddress string
|
||||
LdapBaseDn string
|
||||
LdapCertFile string
|
||||
}
|
||||
|
||||
var systemCA *x509.CertPool
|
||||
var certLoaded bool
|
||||
|
||||
func GetFilePath(path string) string {
|
||||
// Check for empty filename
|
||||
if len(path) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// check if filename exists
|
||||
if _, err := os.Stat(path); os.IsNotExist((err)) {
|
||||
fmt.Printf("File '%s' not found, searching in same directory as binary\n", path)
|
||||
// if not, check that it exists in the same directory as the currently executing binary
|
||||
ex, err2 := os.Executable()
|
||||
if err2 != nil {
|
||||
//log.Printf("Error determining binary path : '%s'", err)
|
||||
return ""
|
||||
}
|
||||
binaryPath := filepath.Dir(ex)
|
||||
path = filepath.Join(binaryPath, path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func LoadLdapCert() {
|
||||
var err error
|
||||
// Get a copy of the system defined CA's
|
||||
systemCA, err = x509.SystemCertPool()
|
||||
if err != nil {
|
||||
fmt.Printf("LoadLdapCert error getting system certificate pool : '%s'\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// only try to load certificate from file if the command line argument was specified
|
||||
ldapCertFile := os.Getenv("LDAP_TRUST_CERT_FILE")
|
||||
if ldapCertFile != "" {
|
||||
// Try to read the file
|
||||
cf, err := os.ReadFile(GetFilePath(ldapCertFile))
|
||||
if err != nil {
|
||||
fmt.Printf("LoadLdapCert error opening LDAP certificate file '%s' : '%s'\n", ldapCertFile, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the certificate from the file
|
||||
cpb, _ := pem.Decode(cf)
|
||||
crt, err := x509.ParseCertificate(cpb.Bytes)
|
||||
//fmt.Printf("Loaded certificate with subject %s\n", crt.Subject)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("LoadLdapCert error processing LDAP certificate file '%s' : '%s'\n", ldapCertFile, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Add custom certificate to the system cert pool
|
||||
systemCA.AddCert(crt)
|
||||
|
||||
certLoaded = true
|
||||
}
|
||||
}
|
||||
|
||||
func VerifyLdapCreds(username string, password string) bool {
|
||||
var ldaps *ldap.Conn
|
||||
var err error
|
||||
ldapServer := os.Getenv("LDAP_BIND_ADDRESS")
|
||||
if ldapServer == "" {
|
||||
fmt.Printf("VerifyLdapCreds no LDAP bind address supplied\n")
|
||||
return false
|
||||
}
|
||||
|
||||
ldapBaseDn := os.Getenv("LDAP_BASE_DN")
|
||||
if ldapBaseDn == "" {
|
||||
fmt.Printf("VerifyLdapCreds no LDAP base DN supplied\n")
|
||||
return false
|
||||
}
|
||||
|
||||
// Set up TLS to use our custom certificate authority passed in cli argument
|
||||
tlsConfig := &tls.Config{
|
||||
RootCAs: systemCA,
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
|
||||
// try connecting to AD via TLS and our custom certificate authority
|
||||
// Add port if not specified in .env file
|
||||
if strings.HasSuffix(ldapServer, ":636") {
|
||||
ldaps, err = ldap.DialTLS("tcp", ldapServer, tlsConfig)
|
||||
} else {
|
||||
ldaps, err = ldap.DialTLS("tcp", fmt.Sprintf("%s:636", ldapServer), tlsConfig)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("VerifyLdapCreds error connecting to LDAP bind address '%s' : '%s'\n", ldapServer, err)
|
||||
return false
|
||||
}
|
||||
|
||||
defer ldaps.Close()
|
||||
|
||||
// try to bind to AD
|
||||
err = ldaps.Bind(username, password)
|
||||
if err != nil {
|
||||
fmt.Printf("VerifyLdapCreds error binding to LDAP with supplied credentials : '%s'\n", err)
|
||||
return false
|
||||
}
|
||||
|
||||
searchReq := ldap.NewSearchRequest(
|
||||
ldapBaseDn,
|
||||
ldap.ScopeBaseObject, // you can also use ldap.ScopeWholeSubtree
|
||||
ldap.NeverDerefAliases,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
"(objectClass=*)",
|
||||
[]string{},
|
||||
nil,
|
||||
)
|
||||
result, err := ldaps.Search(searchReq)
|
||||
if err != nil {
|
||||
fmt.Printf("VerifyLdapCreds search error : '%s'\n", err)
|
||||
return false
|
||||
}
|
||||
|
||||
fmt.Printf("result: %v\n", result)
|
||||
|
||||
return true
|
||||
}
|
@@ -88,12 +88,15 @@ func LoginCheck(username string, password string) (string, error) {
|
||||
// Query database for matching user object
|
||||
err = db.QueryRowx("SELECT * FROM Users WHERE Username=?", username).StructScan(&u)
|
||||
|
||||
log.Printf("LoginCheck retrieved user '%v' from database\n", u)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else {
|
||||
log.Printf("LoginCheck retrieved user '%v' from database\n", u)
|
||||
}
|
||||
|
||||
// TODO : attempt ldap bind
|
||||
VerifyLdapCreds(username, password)
|
||||
|
||||
err = VerifyPassword(password, u.Password)
|
||||
|
||||
if err != nil && err == bcrypt.ErrMismatchedHashAndPassword {
|
||||
|
Reference in New Issue
Block a user