github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/skymarshal/skycmd/flags.go (about) 1 package skycmd 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "reflect" 8 "strings" 9 "time" 10 11 flags "github.com/jessevdk/go-flags" 12 "github.com/mitchellh/mapstructure" 13 "sigs.k8s.io/yaml" 14 15 "github.com/pf-qiu/concourse/v6/atc" 16 "github.com/concourse/flag" 17 ) 18 19 var connectors []*Connector 20 21 func RegisterConnector(connector *Connector) { 22 connectors = append(connectors, connector) 23 } 24 25 func GetConnectors() []*Connector { 26 return connectors 27 } 28 29 func WireConnectors(group *flags.Group) { 30 for _, c := range connectors { 31 description := fmt.Sprintf("%s (%s)", group.ShortDescription, c.Name()) 32 connGroup, _ := group.AddGroup(description, description, c.config) 33 connGroup.Namespace = c.ID() 34 } 35 } 36 37 func WireTeamConnectors(group *flags.Group) { 38 for _, c := range connectors { 39 description := fmt.Sprintf("%s (%s)", group.ShortDescription, c.Name()) 40 connTeamGroup, _ := group.AddGroup(description, description, c.teamConfig) 41 connTeamGroup.Namespace = c.ID() 42 } 43 } 44 45 type AuthFlags struct { 46 SecureCookies bool `long:"cookie-secure" description:"Force sending secure flag on http cookies"` 47 Expiration time.Duration `long:"auth-duration" default:"24h" description:"Length of time for which tokens are valid. Afterwards, users will have to log back in."` 48 SigningKey *flag.PrivateKey `long:"session-signing-key" required:"true" description:"File containing an RSA private key, used to sign auth tokens."` 49 LocalUsers map[string]string `long:"add-local-user" description:"List of username:password combinations for all your local users. The password can be bcrypted - if so, it must have a minimum cost of 10." value-name:"USERNAME:PASSWORD"` 50 Clients map[string]string `long:"add-client" description:"List of client_id:client_secret combinations" value-name:"CLIENT_ID:CLIENT_SECRET"` 51 } 52 53 type AuthTeamFlags struct { 54 LocalUsers []string `long:"local-user" description:"A whitelisted local concourse user. These are the users you've added at web startup with the --add-local-user flag." value-name:"USERNAME"` 55 Config flag.File `short:"c" long:"config" description:"Configuration file for specifying team params"` 56 } 57 58 func (flag *AuthTeamFlags) Format() (atc.TeamAuth, error) { 59 60 if path := flag.Config.Path(); path != "" { 61 return flag.formatFromFile() 62 63 } 64 return flag.formatFromFlags() 65 66 } 67 68 // When formatting from a configuration file we iterate over each connector 69 // type and create a new instance of the TeamConfig object for each connector. 70 // These connectors all have their own unique configuration so we need to use 71 // mapstructure to decode the generic result into a known struct. 72 73 // e.g. 74 // The github connector has configuration for: users, teams, orgs 75 // The cf conncetor has configuration for: users, orgs, spaces 76 77 func (flag *AuthTeamFlags) formatFromFile() (atc.TeamAuth, error) { 78 79 content, err := ioutil.ReadFile(flag.Config.Path()) 80 if err != nil { 81 return nil, err 82 } 83 84 var data struct { 85 Roles []map[string]interface{} `json:"roles"` 86 } 87 if err = yaml.Unmarshal(content, &data); err != nil { 88 return nil, err 89 } 90 91 auth := atc.TeamAuth{} 92 93 for _, role := range data.Roles { 94 roleName := role["name"].(string) 95 96 users := []string{} 97 groups := []string{} 98 99 for _, connector := range connectors { 100 config, ok := role[connector.ID()] 101 if !ok { 102 continue 103 } 104 105 teamConfig, err := connector.newTeamConfig() 106 if err != nil { 107 return nil, err 108 } 109 110 err = mapstructure.Decode(config, &teamConfig) 111 if err != nil { 112 return nil, err 113 } 114 115 for _, user := range teamConfig.GetUsers() { 116 if user != "" { 117 users = append(users, connector.ID()+":"+strings.ToLower(user)) 118 } 119 } 120 121 for _, group := range teamConfig.GetGroups() { 122 if group != "" { 123 groups = append(groups, connector.ID()+":"+strings.ToLower(group)) 124 } 125 } 126 } 127 128 if conf, ok := role["local"].(map[string]interface{}); ok { 129 for _, user := range conf["users"].([]interface{}) { 130 if user != "" { 131 users = append(users, "local:"+strings.ToLower(user.(string))) 132 } 133 } 134 } 135 136 if len(users) == 0 && len(groups) == 0 { 137 continue 138 } 139 140 auth[roleName] = map[string][]string{ 141 "users": users, 142 "groups": groups, 143 } 144 } 145 146 if err := auth.Validate(); err != nil { 147 return nil, err 148 } 149 150 return auth, nil 151 } 152 153 // When formatting team config from the command line flags, the connector's 154 // TeamConfig has already been populated by the flags library. All we need to 155 // do is grab the teamConfig object and extract the users and groups. 156 157 func (flag *AuthTeamFlags) formatFromFlags() (atc.TeamAuth, error) { 158 159 users := []string{} 160 groups := []string{} 161 162 for _, connector := range connectors { 163 164 teamConfig := connector.teamConfig 165 166 for _, user := range teamConfig.GetUsers() { 167 if user != "" { 168 users = append(users, connector.ID()+":"+strings.ToLower(user)) 169 } 170 } 171 172 for _, group := range teamConfig.GetGroups() { 173 if group != "" { 174 groups = append(groups, connector.ID()+":"+strings.ToLower(group)) 175 } 176 } 177 } 178 179 for _, user := range flag.LocalUsers { 180 if user != "" { 181 users = append(users, "local:"+strings.ToLower(user)) 182 } 183 } 184 185 if len(users) == 0 && len(groups) == 0 { 186 return nil, atc.ErrAuthConfigInvalid 187 } 188 189 return atc.TeamAuth{ 190 "owner": map[string][]string{ 191 "users": users, 192 "groups": groups, 193 }, 194 }, nil 195 } 196 197 type Config interface { 198 Name() string 199 Serialize(redirectURI string) ([]byte, error) 200 } 201 202 type TeamConfig interface { 203 GetUsers() []string 204 GetGroups() []string 205 } 206 207 type Connector struct { 208 id string 209 config Config 210 teamConfig TeamConfig 211 } 212 213 func (con *Connector) ID() string { 214 return con.id 215 } 216 217 func (con *Connector) Name() string { 218 return con.config.Name() 219 } 220 221 func (con *Connector) Serialize(redirectURI string) ([]byte, error) { 222 return con.config.Serialize(redirectURI) 223 } 224 225 func (con *Connector) newTeamConfig() (TeamConfig, error) { 226 227 typeof := reflect.TypeOf(con.teamConfig) 228 if typeof.Kind() == reflect.Ptr { 229 typeof = typeof.Elem() 230 } 231 232 valueof := reflect.ValueOf(con.teamConfig) 233 if valueof.Kind() == reflect.Ptr { 234 valueof = valueof.Elem() 235 } 236 237 instance := reflect.New(typeof).Interface() 238 res, ok := instance.(TeamConfig) 239 if !ok { 240 return nil, errors.New("Invalid TeamConfig") 241 } 242 243 return res, nil 244 }