Free-IPA Ldap Intergration with GO - go

I am trying FreeIPA integration with golang using package "gopkg.in/ldap.v2", I created one role with name of "test" in FreeIPA UI and tried to search that role
via command line:
ldapsearch -D "cn=directory manager" -w "*****" -p 389 -h "ec2-test.eu-west-1.compute. amazonaws.com" -b "dc=ec2-test,dc=eu-west-1,dc=compute,dc=amazonaws,dc=com" -v -s sub "(&(objectclass=*)(cn=test))"
Output:
ldap_initialize( ldap://ec2-test.eu-west-1.compute.amazonaws.com:389 ) filter: (&(objectclass=*)(cn=test)) requesting: All userApplication attributes
# extended LDIF
#
# LDAPv3
# base <dc=ec2-test,dc=eu-west-1,dc=compute,dc=amazonaws,dc=com> with scope subtree
# filter: (&(objectclass=*)(cn=test))
# requesting: ALL
#
# test, roles, accounts, ec2-test.eu-west-1.compute.amazonaws.com
dn: cn=test,cn=roles,cn=accounts,dc=ec2-test,dc=eu-west-1,dc=compute,dc=amazonaws,dc=com
objectClass: groupofnames
objectClass: nestedgroup
objectClass: top
cn: test
member: uid=gow,cn=users,cn=accounts,dc=ec2-test,dc=eu-west-1,dc=comp ute,dc=amazonaws,dc=com
member: cn=trov,cn=groups,cn=accounts,dc=ec2-test,dc=eu-west-1,dc=com pute,dc=amazonaws,dc=com
# search result search: 2 result: 0 Success
# numResponses: 2
# numEntries: 1
I am trying to integrate this with my go code.
My go code is:
filterValue := "(&(objectclass="*")(cn="test"))"
searchRequest := ldap.NewSearchRequest(
baseDN, // The base dn to search
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
filterValue, // The filter to apply
[]string{"givenName", "sn", "mail", "uid", "ou", "cn", "dc", "dn"}, // A list attributes to retrieve
nil,
)
sr, err := ldap.Search(searchRequest)
if err!=nil {
fmt.Println("Error: , err)
} else {
fmt.Println("Result: , sr.Entries)
}
Unfortunately I am getting empty entries in sr.Entries
Can someone help me to get this with golang.
Note: Its working fine for users and groups.

You "probably" need to bind before you start the search using something like:
// The username and password we want to check
username := "someuser"
password := "userpassword"
bindusername := "readonly"
bindpassword := "password"
l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
if err != nil {
log.Fatal(err)
}
defer l.Close()
// Reconnect with TLS
err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
if err != nil {
log.Fatal(err)
}
// First bind with a read only user
err = l.Bind(bindusername, bindpassword)
if err != nil {
log.Fatal(err)
}
// Search for the given username
searchRequest := ldap.NewSearchRequest(
"dc=example,dc=com",
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(objectClass=organizationalPerson)(uid=%s))", username),
[]string{"dn"},
nil,
)
Let me know how I can help.

A little late, perhaps, but if you're looking for an HTTP-based API (as you seem to do in one of the answer's comment), you could look at the following article:
http://www.admin-magazine.com/Archive/2016/34/A-REST-interface-for-FreeIPA
There is actually a very complete JSON-RPC API that is accessible via HTTP. The article linked above gives an example of how to use it. Armed with the API browser included in the FreeIPA GUI you should be able to use the HTTP client in Go to code your own functions.
Barring that GitHub has two potential libraries for you:
https://github.com/ubccr/goipa
https://github.com/tehwalris/go-freeipa
The first implements only a few functions, but these could be enough for your needs. The other one is automatically generated but I don't know how good the result is.
Note that I have used neither, so I cannot command on their usability.

Related

Import external user to firebase

I want to import users from an external database to firebase.
The password were hashed with a sha256 function with the password prepended by a salt (which is a UUID).
For example:
password = "123qwerty!"
salt = "cb60eb29-95a2-418e-be2a-c1c107fb1add"
hash = sha256(salt+password)
# 54ccb21d42c6961aa1b666b7cb0485f85aab2f2323399fb2959ea5e4e9f6f595
Now to import this to firebase I would do the following:
users = []*auth.UserToImport
users = append(users, (&auth.UserToImport{}).
UID("some-uid").
Email("jon.foo#example.com").
PasswordHash([]byte("54ccb21d42c6961aa1b666b7cb0485f85aab2f2323399fb2959ea5e4e9f6f595")).
PasswordSalt([]byte("cb60eb29-95a2-418e-be2a-c1c107fb1add")).
DisplayName("Jon FOO"))
h := hash.SHA256{
Rounds: 1,
InputOrder: hash.InputOrderSaltFirst,
}
res, err := cl.ImportUsers(ctx, users, auth.WithHash(h))
if err != nil {
log.Fatal(err)
}
The user is well imported in firebase (I can see it in the console), but when I try to login, I have this error The password is invalid or the user does not have a password.
I cannot see what is wrong with my way, maybe the Rounds parameter should be updated, but to what value?
Thanks!
I finally found my issue.
In my case I was giving as the PasswordHash the hex representation of the password:
PasswordHash([]byte("54ccb21d42c6961aa1b666b7cb0485f85aab2f2323399fb2959ea5e4e9f6f595")).
It turns out I have to decode first the password, like the following:
decoded, err := hex.DecodeString("54ccb21d42c6961aa1b666b7cb0485f85aab2f2323399fb2959ea5e4e9f6f595")
if err != nil {
return err
}
user := (&auth.UserToImport{}).
PasswordHash(decoded).
PasswordSalt([]byte("cb60eb29-95a2-418e-be2a-c1c107fb1add")). // the salt stays the same
...
// call ImportUsers with the same hash configuration (Rounds: 1, InputOrder: SaltFirst)
After updating this I ran the code and I could now authenticate with my imported user.
Quick note: as mentionned in the comment, the node SDK does not have the option to specify the input order (salt or password first), this seems to be an important missing feature.

Google Drive API - List Permissions of a file

I am using the list permissions endpoint (https://developers.google.com/drive/api/v3/reference/permissions/list) to get all the permissions for a file that exists in a shared drive.
I am running into an issue with a file I have that lives in a shared drive. The file has 100 permissions. The max limit on the number of permissions returned for a shared drive file is 100, so I should only need to make one request to get all the permissions for the file and the API should not return a next page token.
But this is not the behaviour I am experiencing, after the first request I continuously get the same next page token back from the API.
So for the following code below (written in go), I get into an infinite loop, since I continuously get the same next page token back.
var permissionService PermissionsService := getPermissionsService()
fileID := "1234abcd"
nextPageToken := ""
anotherPage := true
permissions := []*Permission{}
for anotherPage {
result, err := permissionService.
List(fileID).
SupportsAllDrives(true).
SupportsTeamDrives(false).
Fields("*").
PageSize(100).
Do()
if err != nil {
panic(err)
}
anotherPage = result.NextPageToken != ""
nextPageToken = result.NextPageToken
permissions = append(permissions, result.Permissions...)
}
fmt.Println("Permissions", permissions)
Am I supposed to account for this case in my code? From the documentation, this is never mentioned so I assume this is not supposed to happen.
It seems that you did not add the PageToken parameter in your permissionService. You are actually requesting the same first 100 results of the permission service.
Not familiar with the language but something like this
for anotherPage {
result, err := permissionService.
List(fileID).
SupportsAllDrives(true).
SupportsTeamDrives(false).
Fields("*").
PageSize(100).
PageToken(nextPageToken).
Do()
if err != nil {
panic(err)
}
anotherPage = result.NextPageToken != ""
nextPageToken = result.NextPageToken
permissions = append(permissions, result.Permissions...)
}
Can you also verify first if the result.NextPageToken will return "" if the file has < 100 results, I used API explorer and Apps Script to access this API and the nextPageToken is not part of the response body when permission list is less than the pageSize.

How to add new entry to LDAP server with Golang?

I had an issue trying to add a new entry into my LDAP server. The error I get is the following: LDAP Result Code 65 "Object Class Violation": no structural object class provided. If anyone could give me some input on why, that would be helpful.
I'm binded to the server, so it seems like I just have an issue with the actual entry attributes.. but I'm not sure where to fix it.
package main
import (
"fmt"
// "github.com/go-ldap/ldap"
"gopkg.in/ldap.v2"
"log"
)
//List of constants
const (
host = "127.0.0.1"
port = "389"
hostPort = host + ":" + port
userID = "cn=admin,dc=test123,dc=com"
password = "password"
)
//Main function to be called
func main() {
addEntries()
}
//Add entries function
func addEntries(){
fmt.Println("Adding started")
//Initialize connection
l, err := ldap.Dial("tcp", hostPort)
if err != nil {
log.Fatal(err)
}
defer l.Close()
//Bind to the LDAP server
bindusername := "cn=admin,dc=test123,dc=com"
bindpassword := "password"
err = l.Bind(bindusername, bindpassword)
if err != nil {
log.Fatal(err)
return
}
fmt.Println("Testing.")
//Create new Add request object to be added to LDAP server.
a := ldap.NewAddRequest("ou=groups,dc=test123,dc=com")
a.Attribute("cn", []string{"gotest"})
a.Attribute("objectClass" ,[]string{"top"})
a.Attribute("description", []string{"this is a test to add an entry using golang"})
a.Attribute("sn" ,[]string{"Google"})
fmt.Println("Testing.")
add(a , l)
}
func add(addRequest *ldap.AddRequest , l *ldap.Conn) {
err := l.Add(addRequest)
if err != nil {
fmt.Println("Entry NOT done",err)
} else {
fmt.Println("Entry DONE",err)
}
}
The most common errors when creating LDAP objects is a missing objectClass and/or an object's mandatory attributes (e.g. uid, cn etc.) Here are some techniques to identify these requirements.
You can query an LDAP server's schema like so:
ldapsearch -x -h my.example.com -b "cn=schema" -s base "(objectclass=*)"
The output is not very human-readable, but if you know the exact objectClass you are looking for, things can become a little clearer.
For example the objectClass: person definition may looks like this:
objectClasses: ( 2.5.6.6 NAME 'person' DESC 'Defines entries that generically
represent people.' SUP top STRUCTURAL MUST ( cn $ sn ) MAY ( description $ se
eAlso $ telephoneNumber $ userPassword ) )
So you can see this is a STRUCTURAL objectClass (supplementing top) and if one were to create such a person LDAP object, it:
must include: cn and sn
and optionally: description, seeAlso, telephoneNumber or userPassword
Individual attribute types are defined like so:
attributeTypes: ( 2.5.4.3 NAME ( 'cn' 'commonName' ) DESC 'This is the X.500 c
ommonName attribute, which contains a name of an object. If the object corre
sponds to a person, it is typically the persons full name.' SUP 2.5.4.41 EQUA
LITY 2.5.13.2 ORDERING 2.5.13.3 SUBSTR 2.5.13.4 )

How to check if couchbase document exists, without retrieving full content, using golang SDK?

In my code I want to do or not to do some actions depending on document with given key existence. But can't avoid additional network overhead retrieving all document content.
Now I'm using
cas, err := bucket.Get(key, &value)
And looking for err == gocb.ErrKeyNotFound to determine document missed case.
Is there some more efficient approach?
You can use the sub-document API and check for the existence of a field.
Example from Using the Sub-Document API to get (only) what you want
:
rv = bucket.lookup_in('customer123', SD.exists('purchases.pending[-1]'))
rv.exists(0) # (check if path for first command exists): =>; False
Edit: Add go example
You can use the sub-document API to check for document existence like this:
frag, err := bucket.LookupIn("document-key").
Exists("any-path").Execute()
if err != nil && err == gocb.ErrKeyNotFound {
fmt.Printf("Key does not exist\n")
} else {
if frag.Exists("any-path") {
fmt.Printf("Path exists\n")
} else {
fmt.Printf("Path does not exist\n")
}
}

Different hashes during CRAM-MD5 authentication

As an exercise, I'm trying to implement a mock SMTP server with CRAM-MD5 authentication in Go (without following RFC 2195, since it looks like it doesn't matter to the client what format the pre-hashed challenge is in; I also assume there is only one user "bob" with password "pass"). But I can't seem to get it right as the hash in response is always different from what I have on the server. I send the email using Go as such (running it as a separate package):
{...}
smtp.SendMail("localhost:25", smtp.CRAMMD5Auth("bob", "pass"),
"bob#localhost", []string{"alice#localhost"}, []byte("Hey Alice!\n"))
{...}
Here's what I do when I get the authentication acknowledgement from the client:
{...}
case strings.Contains(ms, "AUTH CRAM-MD5"):
rndbts = make([]byte, 16) // Declared at package level
b64b := make([]byte, base64.StdEncoding.EncodedLen(16))
rand.Read(rndbts)
base64.StdEncoding.Encode(b64b, rndbts)
_, err = conn.Write([]byte(fmt.Sprintf("334 %x\n", b64b)))
{...}
And this is what I do with the client's response:
{...}
{
ms = strings.TrimRight(ms, "\r\n") // The response to the challenge
ds, _ := base64.StdEncoding.DecodeString(ms)
s := strings.Split(string(ds), " ")
login := s[0] // I can get the login from the response.
h := hmac.New(md5.New, []byte("pass"))
h.Write(rndbts)
c := make([]byte, 0, ourHash.Size()) // From smtp/auth.go, not sure why we need this.
validPass := hmac.Equal(h.Sum(c), []byte(s[1]))
{...}
}
{...}
And the validPass is never true. I omitted error handling from the excerpts for brevity, but they're there in the actual code (though they're always nil). Why are the hashes different? I have looked at the source code for net/smtp, and it seems to me that I'm going in the right direction, but not quite.
I hope this helps! Runnable version is at https://play.golang.org/p/-8shx_IcLV. Also note that you'll need to fix your %x (should be %s) so you're sending the right challenge down to the client. Right now I think you're trying to hex-encode your base64 string.
Once you've fixed that, I believe this code should help you to construct the right response string on the server and compare it to what the client sent.
// Example values taken from http://susam.in/blog/auth-cram-md5/
challenge := []byte("<17893.1320679123#tesseract.susam.in>")
username := []byte("alice")
password := []byte("wonderland")
clientResponse := []byte("YWxpY2UgNjRiMmE0M2MxZjZlZDY4MDZhOTgwOTE0ZTIzZTc1ZjA=")
// hash the challenge with the user's password
h := hmac.New(md5.New, password)
h.Write(challenge)
hash := h.Sum(nil)
// encode the result in lowercase hexadecimal
hexEncoded := hex.EncodeToString(hash)
// prepend the username and a space
toEncode := []byte(string(username) + " " + hexEncoded)
// base64-encode the whole thing
b64Result := make([]byte, base64.StdEncoding.EncodedLen(len(toEncode)))
base64.StdEncoding.Encode(b64Result, toEncode)
// check that this is equal to what the client sent
if hmac.Equal(b64Result, clientResponse) {
fmt.Println("Matches!")
}

Resources