Browse Source

Yaml Config File, Config File Flag and documentation of the feature

pull/7/head
localleon 4 years ago
parent
commit
22cc747cf0
  1. 7
      README.md
  2. 20
      config.yaml
  3. 141
      gitea-group-sync.go
  4. 1
      go.mod
  5. 19
      types.go

7
README.md

@ -16,8 +16,15 @@ If you configured the [Gitea](https://hub.docker.com/r/gitea/gitea) <=> [LDAP](h


You need to create Manage Access Tokens and add key to run.sh or docker-compose.yml the configuration file You need to create Manage Access Tokens and add key to run.sh or docker-compose.yml the configuration file


##### Configuration:
There are two ways to configure the application. Via YAML Configuration File or Enviroment Variables.
- See `run.sh` for an example using the enviroment Variables.
- Use `./gitea-group-sync --config="config.yaml"` with the example Config File for the YAML Variant.

##### Gitea Tokens
The application supports several keys, since to add people to the group you must be the owner of the organization. The application supports several keys, since to add people to the group you must be the owner of the organization.



![](images/Image2.png) ![](images/Image2.png)


#### create organizations in gitea #### create organizations in gitea

20
config.yaml

@ -0,0 +1,20 @@
# Example Configuration for gitea-group-sync

ApiKeys:
TokenKey:
- "c00c810bb668c63ce7cd8057411d2f560eac469c,2c02df6959d012dee8f5da3539f63223417c4bbe"
BaseUrl: "http://localhost:3200"

# LDAP Config
LdapURL: "localhost"
LdapPort: 639
LdapTLS: false
LdapBindDN: "cn=admin,dc=planetexpress,dc=com"
LdapBindPassword: "GoodNewsEveryone"
LdapFilter: '(&(objectClass=person)(memberOf=cn=%s,ou=people,dc=planetexpress,dc=com))'
LdapUserSearchBase: 'ou=people,dc=planetexpress,dc=com'
ReqTime: '@every 1m'
LdapUserIdentityAttribute: "uid"
LdapUserFullName: "sn" # can be changed to "cn" if needed


141
gitea-group-sync.go

@ -2,6 +2,7 @@ package main


import ( import (
"crypto/tls" "crypto/tls"
"flag"
"fmt" "fmt"
"log" "log"
"net/url" "net/url"
@ -12,6 +13,7 @@ import (


"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
"gopkg.in/ldap.v3" "gopkg.in/ldap.v3"
"gopkg.in/yaml.v2"
) )


func AddUsersToTeam(apiKeys GiteaKeys, users []Account, team int) bool { func AddUsersToTeam(apiKeys GiteaKeys, users []Account, team int) bool {
@ -50,8 +52,11 @@ func DelUsersFromTeam(apiKeys GiteaKeys, Users []Account, team int) bool {
return true return true
} }


func main() { var configFlag = flag.String("config", "config.yaml", "Specify YAML Configuration File")


func main() {
// Parse flags of programm
flag.Parse()
mainJob() // First run for check settings mainJob() // First run for check settings


var repTime string var repTime string
@ -70,62 +75,47 @@ func main() {
} }
} }


// Config describes the settings of the application. This structure is used in the settings-import process
type Config struct {
apiKeys GiteaKeys
ldapUrl string
ldapPort int
ldapTls bool
ldapBindDN string
ldapBindPassword string
ldapFilter string
ldapUserSearchBase string
repTime string
ldapUserIdentityAttribute string
ldapUserFullName string
}

// This Function parses the enviroment for application specific variables and returns a Config struct. // This Function parses the enviroment for application specific variables and returns a Config struct.
// Used for setting all required settings in the application // Used for setting all required settings in the application
func importEnvVars() Config { func importEnvVars() Config {


// Create temporary structs for creating the final config // Create temporary structs for creating the final config
envConfig := Config{} envConfig := Config{}
envConfig.apiKeys = GiteaKeys{} envConfig.ApiKeys = GiteaKeys{}


// Start parsing env. Variables // Start parsing env. Variables
if len(os.Getenv("GITEA_TOKEN")) < 40 { // get on https://[web_site_url]/user/settings/applications if len(os.Getenv("GITEA_TOKEN")) < 40 { // get on https://[web_site_url]/user/settings/applications
log.Println("GITEA_TOKEN is empty or invalid.") log.Println("GITEA_TOKEN is empty or invalid.")
} else { } else {
envConfig.apiKeys.TokenKey = strings.Split(os.Getenv("GITEA_TOKEN"), ",") envConfig.ApiKeys.TokenKey = strings.Split(os.Getenv("GITEA_TOKEN"), ",")
} }


if len(os.Getenv("GITEA_URL")) == 0 { if len(os.Getenv("GITEA_URL")) == 0 {
log.Println("GITEA_URL is empty") log.Println("GITEA_URL is empty")
} else { } else {
envConfig.apiKeys.BaseUrl = os.Getenv("GITEA_URL") envConfig.ApiKeys.BaseUrl = os.Getenv("GITEA_URL")
} }


if len(os.Getenv("LDAP_URL")) == 0 { if len(os.Getenv("LDAP_URL")) == 0 {
log.Println("LDAP_URL is empty") log.Println("LDAP_URL is empty")
} else { } else {
envConfig.ldapUrl = os.Getenv("LDAP_URL") envConfig.LdapURL = os.Getenv("LDAP_URL")
} }


if len(os.Getenv("LDAP_TLS_PORT")) > 0 { if len(os.Getenv("LDAP_TLS_PORT")) > 0 {
port, err := strconv.Atoi(os.Getenv("LDAP_TLS_PORT")) port, err := strconv.Atoi(os.Getenv("LDAP_TLS_PORT"))
envConfig.ldapPort = port envConfig.LdapPort = port
envConfig.ldapTls = true envConfig.LdapTLS = true
log.Printf("DialTLS:=%v:%d", envConfig.ldapUrl, envConfig.ldapPort) log.Printf("DialTLS:=%v:%d", envConfig.LdapURL, envConfig.LdapPort)
if err != nil { if err != nil {
log.Println("LDAP_TLS_PORT is invalid.") log.Println("LDAP_TLS_PORT is invalid.")
} }
} else { } else {
if len(os.Getenv("LDAP_PORT")) > 0 { if len(os.Getenv("LDAP_PORT")) > 0 {
port, err := strconv.Atoi(os.Getenv("LDAP_PORT")) port, err := strconv.Atoi(os.Getenv("LDAP_PORT"))
envConfig.ldapPort = port envConfig.LdapPort = port
envConfig.ldapTls = false envConfig.LdapTLS = false
log.Printf("Dial:=%v:%d", envConfig.ldapUrl, envConfig.ldapPort) log.Printf("Dial:=%v:%d", envConfig.LdapURL, envConfig.LdapPort)
if err != nil { if err != nil {
log.Println("LDAP_PORT is invalid.") log.Println("LDAP_PORT is invalid.")
} }
@ -135,60 +125,85 @@ func importEnvVars() Config {
if len(os.Getenv("BIND_DN")) == 0 { if len(os.Getenv("BIND_DN")) == 0 {
log.Println("BIND_DN is empty") log.Println("BIND_DN is empty")
} else { } else {
envConfig.ldapBindDN = os.Getenv("BIND_DN") envConfig.LdapBindDN = os.Getenv("BIND_DN")
} }


if len(os.Getenv("BIND_PASSWORD")) == 0 { if len(os.Getenv("BIND_PASSWORD")) == 0 {
log.Println("BIND_PASSWORD is empty") log.Println("BIND_PASSWORD is empty")
} else { } else {
envConfig.ldapBindPassword = os.Getenv("BIND_PASSWORD") envConfig.LdapBindPassword = os.Getenv("BIND_PASSWORD")
} }


if len(os.Getenv("LDAP_FILTER")) == 0 { if len(os.Getenv("LDAP_FILTER")) == 0 {
log.Println("LDAP_FILTER is empty") log.Println("LDAP_FILTER is empty")
} else { } else {
envConfig.ldapFilter = os.Getenv("LDAP_FILTER") envConfig.LdapFilter = os.Getenv("LDAP_FILTER")
} }


if len(os.Getenv("LDAP_USER_SEARCH_BASE")) == 0 { if len(os.Getenv("LDAP_USER_SEARCH_BASE")) == 0 {
log.Println("LDAP_USER_SEARCH_BASE is empty") log.Println("LDAP_USER_SEARCH_BASE is empty")
} else { } else {
envConfig.ldapUserSearchBase = os.Getenv("LDAP_USER_SEARCH_BASE") envConfig.LdapUserSearchBase = os.Getenv("LDAP_USER_SEARCH_BASE")
} }


if len(os.Getenv("LDAP_USER_IDENTITY_ATTRIBUTE")) == 0 { if len(os.Getenv("LDAP_USER_IDENTITY_ATTRIBUTE")) == 0 {
envConfig.ldapUserIdentityAttribute = "uid" envConfig.LdapUserIdentityAttribute = "uid"
log.Println("By default LDAP_USER_IDENTITY_ATTRIBUTE = 'uid'") log.Println("By default LDAP_USER_IDENTITY_ATTRIBUTE = 'uid'")
} else { } else {
envConfig.ldapUserIdentityAttribute = os.Getenv("LDAP_USER_IDENTITY_ATTRIBUTE") envConfig.LdapUserIdentityAttribute = os.Getenv("LDAP_USER_IDENTITY_ATTRIBUTE")
} }


if len(os.Getenv("LDAP_USER_FULL_NAME")) == 0 { if len(os.Getenv("LDAP_USER_FULL_NAME")) == 0 {
envConfig.ldapUserFullName = "sn" //change to cn if you need it envConfig.LdapUserFullName = "sn" //change to cn if you need it
log.Println("By default LDAP_USER_FULL_NAME = 'sn'") log.Println("By default LDAP_USER_FULL_NAME = 'sn'")
} else { } else {
envConfig.ldapUserFullName = os.Getenv("LDAP_USER_FULL_NAME") envConfig.LdapUserFullName = os.Getenv("LDAP_USER_FULL_NAME")
} }


return envConfig // return the config struct for use. return envConfig // return the config struct for use.
} }


func importYAMLConfig(path string) (Config, error) {
// Open Config File
f, err := os.Open(path)
if err != nil {
return Config{}, err // Aborting
}
defer f.Close()

// Parse File into Config Struct
var cfg Config
decoder := yaml.NewDecoder(f)
err = decoder.Decode(&cfg)
if err != nil {
return Config{}, err // Aborting
}
return cfg, nil
}

func mainJob() { func mainJob() {


//------------------------------ //------------------------------
// Check and Set input settings // Check and Set input settings
//------------------------------ //------------------------------
var cfg Config


cfg, importErr := importYAMLConfig(*configFlag)
if importErr != nil {
log.Println("Fallback: Importing Settings from Enviroment Variables ") log.Println("Fallback: Importing Settings from Enviroment Variables ")
cfg := importEnvVars() cfg = importEnvVars()
} else {
log.Println("Successfully imported YAML Config from " + *configFlag)
fmt.Println(cfg)
}


// Prepare LDAP Connection // Prepare LDAP Connection
var l *ldap.Conn var l *ldap.Conn
var err error var err error
if cfg.ldapTls { if cfg.LdapTLS {
l, err = ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", cfg.ldapUrl, cfg.ldapPort), &tls.Config{InsecureSkipVerify: true}) l, err = ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", cfg.LdapURL, cfg.LdapPort), &tls.Config{InsecureSkipVerify: true})
} else { } else {
l, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", cfg.ldapUrl, cfg.ldapPort)) l, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", cfg.LdapURL, cfg.LdapPort))
} }


if err != nil { if err != nil {
@ -198,16 +213,16 @@ func mainJob() {
} }
defer l.Close() defer l.Close()


err = l.Bind(cfg.ldapBindDN, cfg.ldapBindPassword) err = l.Bind(cfg.LdapBindDN, cfg.LdapBindPassword)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
page := 1 page := 1
cfg.apiKeys.BruteforceTokenKey = 0 cfg.ApiKeys.BruteforceTokenKey = 0
cfg.apiKeys.Command = "/api/v1/admin/orgs?page=" + fmt.Sprintf("%d", page) + "&limit=20&access_token=" // List all organizations cfg.ApiKeys.Command = "/api/v1/admin/orgs?page=" + fmt.Sprintf("%d", page) + "&limit=20&access_token=" // List all organizations
organizationList := RequestOrganizationList(cfg.apiKeys) organizationList := RequestOrganizationList(cfg.ApiKeys)


log.Printf("%d organizations were found on the server: %s", len(organizationList), cfg.apiKeys.BaseUrl) log.Printf("%d organizations were found on the server: %s", len(organizationList), cfg.ApiKeys.BaseUrl)


for 1 < len(organizationList) { for 1 < len(organizationList) {


@ -217,21 +232,21 @@ func mainJob() {


log.Printf("Begin an organization review: OrganizationName= %v, OrganizationId= %d \n", organizationList[i].Name, organizationList[i].Id) log.Printf("Begin an organization review: OrganizationName= %v, OrganizationId= %d \n", organizationList[i].Name, organizationList[i].Id)


cfg.apiKeys.Command = "/api/v1/orgs/" + organizationList[i].Name + "/teams?access_token=" cfg.ApiKeys.Command = "/api/v1/orgs/" + organizationList[i].Name + "/teams?access_token="
teamList := RequestTeamList(cfg.apiKeys) teamList := RequestTeamList(cfg.ApiKeys)
log.Printf("%d teams were found in %s organization", len(teamList), organizationList[i].Name) log.Printf("%d teams were found in %s organization", len(teamList), organizationList[i].Name)
log.Printf("Skip synchronization in the Owners team") log.Printf("Skip synchronization in the Owners team")
cfg.apiKeys.BruteforceTokenKey = 0 cfg.ApiKeys.BruteforceTokenKey = 0


for j := 1; j < len(teamList); j++ { for j := 1; j < len(teamList); j++ {


// preparing request to ldap server // preparing request to ldap server
filter := fmt.Sprintf(cfg.ldapFilter, teamList[j].Name) filter := fmt.Sprintf(cfg.LdapFilter, teamList[j].Name)
searchRequest := ldap.NewSearchRequest( searchRequest := ldap.NewSearchRequest(
cfg.ldapUserSearchBase, // The base dn to search cfg.LdapUserSearchBase, // The base dn to search
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
filter, // The filter to apply filter, // The filter to apply
[]string{"cn", "uid", "mailPrimaryAddress, sn", cfg.ldapUserIdentityAttribute}, // A list attributes to retrieve []string{"cn", "uid", "mailPrimaryAddress, sn", cfg.LdapUserIdentityAttribute}, // A list attributes to retrieve
nil, nil,
) )
// make request to ldap server // make request to ldap server
@ -243,18 +258,18 @@ func mainJob() {
AccountsGitea := make(map[string]Account) AccountsGitea := make(map[string]Account)
var addUserToTeamList, delUserToTeamlist []Account var addUserToTeamList, delUserToTeamlist []Account
if len(sr.Entries) > 0 { if len(sr.Entries) > 0 {
log.Printf("The LDAP %s has %d users corresponding to team %s", cfg.ldapUrl, len(sr.Entries), teamList[j].Name) log.Printf("The LDAP %s has %d users corresponding to team %s", cfg.LdapURL, len(sr.Entries), teamList[j].Name)
for _, entry := range sr.Entries { for _, entry := range sr.Entries {


AccountsLdap[entry.GetAttributeValue(cfg.ldapUserIdentityAttribute)] = Account{ AccountsLdap[entry.GetAttributeValue(cfg.LdapUserIdentityAttribute)] = Account{
Full_name: entry.GetAttributeValue(cfg.ldapUserFullName), Full_name: entry.GetAttributeValue(cfg.LdapUserFullName),
Login: entry.GetAttributeValue(cfg.ldapUserIdentityAttribute), Login: entry.GetAttributeValue(cfg.LdapUserIdentityAttribute),
} }
} }


cfg.apiKeys.Command = "/api/v1/teams/" + fmt.Sprintf("%d", teamList[j].Id) + "/members?access_token=" cfg.ApiKeys.Command = "/api/v1/teams/" + fmt.Sprintf("%d", teamList[j].Id) + "/members?access_token="
AccountsGitea, cfg.apiKeys.BruteforceTokenKey = RequestUsersList(cfg.apiKeys) AccountsGitea, cfg.ApiKeys.BruteforceTokenKey = RequestUsersList(cfg.ApiKeys)
log.Printf("The gitea %s has %d users corresponding to team %s Teamid=%d", cfg.apiKeys.BaseUrl, len(AccountsGitea), teamList[j].Name, teamList[j].Id) log.Printf("The gitea %s has %d users corresponding to team %s Teamid=%d", cfg.ApiKeys.BaseUrl, len(AccountsGitea), teamList[j].Name, teamList[j].Id)


for k, v := range AccountsLdap { for k, v := range AccountsLdap {
if AccountsGitea[k].Login != v.Login { if AccountsGitea[k].Login != v.Login {
@ -262,7 +277,7 @@ func mainJob() {
} }
} }
log.Printf("can be added users list %v", addUserToTeamList) log.Printf("can be added users list %v", addUserToTeamList)
AddUsersToTeam(cfg.apiKeys, addUserToTeamList, teamList[j].Id) AddUsersToTeam(cfg.ApiKeys, addUserToTeamList, teamList[j].Id)


for k, v := range AccountsGitea { for k, v := range AccountsGitea {
if AccountsLdap[k].Login != v.Login { if AccountsLdap[k].Login != v.Login {
@ -270,18 +285,18 @@ func mainJob() {
} }
} }
log.Printf("must be del users list %v", delUserToTeamlist) log.Printf("must be del users list %v", delUserToTeamlist)
DelUsersFromTeam(cfg.apiKeys, delUserToTeamlist, teamList[j].Id) DelUsersFromTeam(cfg.ApiKeys, delUserToTeamlist, teamList[j].Id)


} else { } else {
log.Printf("The LDAP %s not found users corresponding to team %s", cfg.ldapUrl, teamList[j].Name) log.Printf("The LDAP %s not found users corresponding to team %s", cfg.LdapURL, teamList[j].Name)
} }
} }
} }


page++ page++
cfg.apiKeys.BruteforceTokenKey = 0 cfg.ApiKeys.BruteforceTokenKey = 0
cfg.apiKeys.Command = "/api/v1/admin/orgs?page=" + fmt.Sprintf("%d", page) + "&limit=20&access_token=" // List all organizations cfg.ApiKeys.Command = "/api/v1/admin/orgs?page=" + fmt.Sprintf("%d", page) + "&limit=20&access_token=" // List all organizations
organizationList = RequestOrganizationList(cfg.apiKeys) organizationList = RequestOrganizationList(cfg.ApiKeys)
log.Printf("%d organizations were found on the server: %s", len(organizationList), cfg.apiKeys.BaseUrl) log.Printf("%d organizations were found on the server: %s", len(organizationList), cfg.ApiKeys.BaseUrl)
} }
} }

1
go.mod

@ -5,4 +5,5 @@ go 1.14
require ( require (
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
gopkg.in/ldap.v3 v3.1.0 gopkg.in/ldap.v3 v3.1.0
gopkg.in/yaml.v2 v2.3.0
) )

19
types.go

@ -41,8 +41,23 @@ type SearchResults struct {
} }


type GiteaKeys struct { type GiteaKeys struct {
TokenKey []string TokenKey []string `yaml:"TokenKey"`
BaseUrl string BaseUrl string `yaml:"BaseUrl"`
Command string Command string
BruteforceTokenKey int BruteforceTokenKey int
} }

// Config describes the settings of the application. This structure is used in the settings-import process
type Config struct {
ApiKeys GiteaKeys `yaml:"ApiKeys"`
LdapURL string `yaml:"ldapurl"`
LdapPort int `yaml:"LdapPort"`
LdapTLS bool `yaml:"LdapTLS"`
LdapBindDN string `yaml:"LdapBindDN"`
LdapBindPassword string `yaml:"LdapBindPassword"`
LdapFilter string `yaml:"LdapFilter"`
LdapUserSearchBase string `yaml:"LdapUserSearchBase"`
ReqTime string `yaml:"ReqTime"`
LdapUserIdentityAttribute string `yaml:"LdapUserIdentityAttribute"`
LdapUserFullName string `yaml:"LdapUserFullName"`
}

Loading…
Cancel
Save