From 22cc747cf08263fcc74678d57cb385148704f362 Mon Sep 17 00:00:00 2001 From: localleon Date: Mon, 28 Sep 2020 16:40:27 +0200 Subject: [PATCH] Yaml Config File, Config File Flag and documentation of the feature --- README.md | 7 +++ config.yaml | 20 +++++++ gitea-group-sync.go | 143 ++++++++++++++++++++++++-------------------- go.mod | 1 + types.go | 19 +++++- 5 files changed, 124 insertions(+), 66 deletions(-) create mode 100644 config.yaml diff --git a/README.md b/README.md index d5e63bc..2665521 100644 --- a/README.md +++ b/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 +##### 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. + ![](images/Image2.png) #### create organizations in gitea diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..4773612 --- /dev/null +++ b/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 + + diff --git a/gitea-group-sync.go b/gitea-group-sync.go index f5545bb..78182ed 100644 --- a/gitea-group-sync.go +++ b/gitea-group-sync.go @@ -2,6 +2,7 @@ package main import ( "crypto/tls" + "flag" "fmt" "log" "net/url" @@ -12,6 +13,7 @@ import ( "github.com/robfig/cron/v3" "gopkg.in/ldap.v3" + "gopkg.in/yaml.v2" ) func AddUsersToTeam(apiKeys GiteaKeys, users []Account, team int) bool { @@ -50,8 +52,11 @@ func DelUsersFromTeam(apiKeys GiteaKeys, Users []Account, team int) bool { 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 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. // Used for setting all required settings in the application func importEnvVars() Config { // Create temporary structs for creating the final config envConfig := Config{} - envConfig.apiKeys = GiteaKeys{} + envConfig.ApiKeys = GiteaKeys{} // Start parsing env. Variables if len(os.Getenv("GITEA_TOKEN")) < 40 { // get on https://[web_site_url]/user/settings/applications log.Println("GITEA_TOKEN is empty or invalid.") } 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 { log.Println("GITEA_URL is empty") } else { - envConfig.apiKeys.BaseUrl = os.Getenv("GITEA_URL") + envConfig.ApiKeys.BaseUrl = os.Getenv("GITEA_URL") } if len(os.Getenv("LDAP_URL")) == 0 { log.Println("LDAP_URL is empty") } else { - envConfig.ldapUrl = os.Getenv("LDAP_URL") + envConfig.LdapURL = os.Getenv("LDAP_URL") } if len(os.Getenv("LDAP_TLS_PORT")) > 0 { port, err := strconv.Atoi(os.Getenv("LDAP_TLS_PORT")) - envConfig.ldapPort = port - envConfig.ldapTls = true - log.Printf("DialTLS:=%v:%d", envConfig.ldapUrl, envConfig.ldapPort) + envConfig.LdapPort = port + envConfig.LdapTLS = true + log.Printf("DialTLS:=%v:%d", envConfig.LdapURL, envConfig.LdapPort) if err != nil { log.Println("LDAP_TLS_PORT is invalid.") } } else { if len(os.Getenv("LDAP_PORT")) > 0 { port, err := strconv.Atoi(os.Getenv("LDAP_PORT")) - envConfig.ldapPort = port - envConfig.ldapTls = false - log.Printf("Dial:=%v:%d", envConfig.ldapUrl, envConfig.ldapPort) + envConfig.LdapPort = port + envConfig.LdapTLS = false + log.Printf("Dial:=%v:%d", envConfig.LdapURL, envConfig.LdapPort) if err != nil { log.Println("LDAP_PORT is invalid.") } @@ -135,60 +125,85 @@ func importEnvVars() Config { if len(os.Getenv("BIND_DN")) == 0 { log.Println("BIND_DN is empty") } else { - envConfig.ldapBindDN = os.Getenv("BIND_DN") + envConfig.LdapBindDN = os.Getenv("BIND_DN") } if len(os.Getenv("BIND_PASSWORD")) == 0 { log.Println("BIND_PASSWORD is empty") } else { - envConfig.ldapBindPassword = os.Getenv("BIND_PASSWORD") + envConfig.LdapBindPassword = os.Getenv("BIND_PASSWORD") } if len(os.Getenv("LDAP_FILTER")) == 0 { log.Println("LDAP_FILTER is empty") } else { - envConfig.ldapFilter = os.Getenv("LDAP_FILTER") + envConfig.LdapFilter = os.Getenv("LDAP_FILTER") } if len(os.Getenv("LDAP_USER_SEARCH_BASE")) == 0 { log.Println("LDAP_USER_SEARCH_BASE is empty") } 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 { - envConfig.ldapUserIdentityAttribute = "uid" + envConfig.LdapUserIdentityAttribute = "uid" log.Println("By default LDAP_USER_IDENTITY_ATTRIBUTE = 'uid'") } 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 { - 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'") } 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. } +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() { //------------------------------ // Check and Set input settings //------------------------------ + var cfg Config - log.Println("Fallback: Importing Settings from Enviroment Variables ") - cfg := importEnvVars() + cfg, importErr := importYAMLConfig(*configFlag) + if importErr != nil { + log.Println("Fallback: Importing Settings from Enviroment Variables ") + cfg = importEnvVars() + } else { + log.Println("Successfully imported YAML Config from " + *configFlag) + fmt.Println(cfg) + } // Prepare LDAP Connection var l *ldap.Conn var err error - if cfg.ldapTls { - l, err = ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", cfg.ldapUrl, cfg.ldapPort), &tls.Config{InsecureSkipVerify: true}) + if cfg.LdapTLS { + l, err = ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", cfg.LdapURL, cfg.LdapPort), &tls.Config{InsecureSkipVerify: true}) } 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 { @@ -198,16 +213,16 @@ func mainJob() { } defer l.Close() - err = l.Bind(cfg.ldapBindDN, cfg.ldapBindPassword) + err = l.Bind(cfg.LdapBindDN, cfg.LdapBindPassword) if err != nil { log.Fatal(err) } page := 1 - cfg.apiKeys.BruteforceTokenKey = 0 - cfg.apiKeys.Command = "/api/v1/admin/orgs?page=" + fmt.Sprintf("%d", page) + "&limit=20&access_token=" // List all organizations - organizationList := RequestOrganizationList(cfg.apiKeys) + cfg.ApiKeys.BruteforceTokenKey = 0 + cfg.ApiKeys.Command = "/api/v1/admin/orgs?page=" + fmt.Sprintf("%d", page) + "&limit=20&access_token=" // List all organizations + 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) { @@ -217,21 +232,21 @@ func mainJob() { 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=" - teamList := RequestTeamList(cfg.apiKeys) + cfg.ApiKeys.Command = "/api/v1/orgs/" + organizationList[i].Name + "/teams?access_token=" + teamList := RequestTeamList(cfg.ApiKeys) log.Printf("%d teams were found in %s organization", len(teamList), organizationList[i].Name) log.Printf("Skip synchronization in the Owners team") - cfg.apiKeys.BruteforceTokenKey = 0 + cfg.ApiKeys.BruteforceTokenKey = 0 for j := 1; j < len(teamList); j++ { // preparing request to ldap server - filter := fmt.Sprintf(cfg.ldapFilter, teamList[j].Name) + filter := fmt.Sprintf(cfg.LdapFilter, teamList[j].Name) searchRequest := ldap.NewSearchRequest( - cfg.ldapUserSearchBase, // The base dn to search + cfg.LdapUserSearchBase, // The base dn to search ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, 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, ) // make request to ldap server @@ -243,18 +258,18 @@ func mainJob() { AccountsGitea := make(map[string]Account) var addUserToTeamList, delUserToTeamlist []Account 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 { - AccountsLdap[entry.GetAttributeValue(cfg.ldapUserIdentityAttribute)] = Account{ - Full_name: entry.GetAttributeValue(cfg.ldapUserFullName), - Login: entry.GetAttributeValue(cfg.ldapUserIdentityAttribute), + AccountsLdap[entry.GetAttributeValue(cfg.LdapUserIdentityAttribute)] = Account{ + Full_name: entry.GetAttributeValue(cfg.LdapUserFullName), + Login: entry.GetAttributeValue(cfg.LdapUserIdentityAttribute), } } - cfg.apiKeys.Command = "/api/v1/teams/" + fmt.Sprintf("%d", teamList[j].Id) + "/members?access_token=" - 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) + cfg.ApiKeys.Command = "/api/v1/teams/" + fmt.Sprintf("%d", teamList[j].Id) + "/members?access_token=" + 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) for k, v := range AccountsLdap { if AccountsGitea[k].Login != v.Login { @@ -262,7 +277,7 @@ func mainJob() { } } 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 { if AccountsLdap[k].Login != v.Login { @@ -270,18 +285,18 @@ func mainJob() { } } log.Printf("must be del users list %v", delUserToTeamlist) - DelUsersFromTeam(cfg.apiKeys, delUserToTeamlist, teamList[j].Id) + DelUsersFromTeam(cfg.ApiKeys, delUserToTeamlist, teamList[j].Id) } 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++ - cfg.apiKeys.BruteforceTokenKey = 0 - cfg.apiKeys.Command = "/api/v1/admin/orgs?page=" + fmt.Sprintf("%d", page) + "&limit=20&access_token=" // List all organizations - organizationList = RequestOrganizationList(cfg.apiKeys) - log.Printf("%d organizations were found on the server: %s", len(organizationList), cfg.apiKeys.BaseUrl) + cfg.ApiKeys.BruteforceTokenKey = 0 + cfg.ApiKeys.Command = "/api/v1/admin/orgs?page=" + fmt.Sprintf("%d", page) + "&limit=20&access_token=" // List all organizations + organizationList = RequestOrganizationList(cfg.ApiKeys) + log.Printf("%d organizations were found on the server: %s", len(organizationList), cfg.ApiKeys.BaseUrl) } } diff --git a/go.mod b/go.mod index a49b0e6..78bcb52 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,5 @@ go 1.14 require ( github.com/robfig/cron/v3 v3.0.1 gopkg.in/ldap.v3 v3.1.0 + gopkg.in/yaml.v2 v2.3.0 ) diff --git a/types.go b/types.go index 378aa27..bb5aae3 100644 --- a/types.go +++ b/types.go @@ -41,8 +41,23 @@ type SearchResults struct { } type GiteaKeys struct { - TokenKey []string - BaseUrl string + TokenKey []string `yaml:"TokenKey"` + BaseUrl string `yaml:"BaseUrl"` Command string 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"` +}