github.com/cyverse/go-irodsclient@v0.13.2/irods/types/account.go (about) 1 package types 2 3 import ( 4 "regexp" 5 6 "github.com/cyverse/go-irodsclient/irods/common" 7 "golang.org/x/xerrors" 8 "gopkg.in/yaml.v3" 9 ) 10 11 const ( 12 // PamTTLDefault is a default value for Pam TTL 13 PamTTLDefault int = 1 14 UsernameRegexString string = "^((\\w|[-.@])+)$" 15 ) 16 17 // IRODSAccount contains irods login information 18 type IRODSAccount struct { 19 AuthenticationScheme AuthScheme 20 ClientServerNegotiation bool 21 CSNegotiationPolicy CSNegotiationRequire 22 Host string 23 Port int 24 ClientUser string 25 ClientZone string 26 ProxyUser string 27 ProxyZone string 28 Password string 29 Ticket string 30 DefaultResource string 31 PamTTL int 32 SSLConfiguration *IRODSSSLConfig 33 } 34 35 // CreateIRODSAccount creates IRODSAccount 36 func CreateIRODSAccount(host string, port int, user string, zone string, 37 authScheme AuthScheme, password string, defaultResource string) (*IRODSAccount, error) { 38 account := &IRODSAccount{ 39 AuthenticationScheme: authScheme, 40 ClientServerNegotiation: false, 41 CSNegotiationPolicy: CSNegotiationDontCare, 42 Host: host, 43 Port: port, 44 ClientUser: user, 45 ClientZone: zone, 46 ProxyUser: user, 47 ProxyZone: zone, 48 Password: password, 49 Ticket: "", 50 DefaultResource: defaultResource, 51 PamTTL: PamTTLDefault, 52 SSLConfiguration: nil, 53 } 54 55 account.FixAuthConfiguration() 56 57 return account, nil 58 } 59 60 // CreateIRODSAccountForTicket creates IRODSAccount 61 func CreateIRODSAccountForTicket(host string, port int, user string, zone string, 62 authScheme AuthScheme, password string, ticket string, defaultResource string) (*IRODSAccount, error) { 63 account := &IRODSAccount{ 64 AuthenticationScheme: authScheme, 65 ClientServerNegotiation: false, 66 CSNegotiationPolicy: CSNegotiationDontCare, 67 Host: host, 68 Port: port, 69 ClientUser: user, 70 ClientZone: zone, 71 ProxyUser: user, 72 ProxyZone: zone, 73 Password: password, 74 Ticket: ticket, 75 DefaultResource: defaultResource, 76 PamTTL: PamTTLDefault, 77 SSLConfiguration: nil, 78 } 79 80 account.FixAuthConfiguration() 81 82 return account, nil 83 } 84 85 // CreateIRODSProxyAccount creates IRODSAccount for proxy access 86 func CreateIRODSProxyAccount(host string, port int, clientUser string, clientZone string, 87 proxyUser string, proxyZone string, 88 authScheme AuthScheme, password string, defaultResource string) (*IRODSAccount, error) { 89 account := &IRODSAccount{ 90 AuthenticationScheme: authScheme, 91 ClientServerNegotiation: false, 92 CSNegotiationPolicy: CSNegotiationDontCare, 93 Host: host, 94 Port: port, 95 ClientUser: clientUser, 96 ClientZone: clientZone, 97 ProxyUser: proxyUser, 98 ProxyZone: proxyZone, 99 Password: password, 100 Ticket: "", 101 DefaultResource: defaultResource, 102 PamTTL: PamTTLDefault, 103 SSLConfiguration: nil, 104 } 105 106 account.FixAuthConfiguration() 107 108 return account, nil 109 } 110 111 // CreateIRODSAccountFromYAML creates IRODSAccount from YAML 112 func CreateIRODSAccountFromYAML(yamlBytes []byte) (*IRODSAccount, error) { 113 y := make(map[string]interface{}) 114 115 err := yaml.Unmarshal(yamlBytes, &y) 116 if err != nil { 117 return nil, xerrors.Errorf("failed to unmarshal yaml to map: %w", err) 118 } 119 120 authScheme := AuthSchemeNative 121 if val, ok := y["auth_scheme"]; ok { 122 authScheme, err = GetAuthScheme(val.(string)) 123 if err != nil { 124 authScheme = AuthSchemeNative 125 } 126 } 127 128 csNegotiation := false 129 if val, ok := y["cs_negotiation"]; ok { 130 csNegotiation = val.(bool) 131 } 132 133 csNegotiationPolicy := CSNegotiationDontCare 134 if val, ok := y["cs_negotiation_policy"]; ok { 135 csNegotiationPolicy, err = GetCSNegotiationRequire(val.(string)) 136 if err != nil { 137 csNegotiationPolicy = CSNegotiationDontCare 138 } 139 } 140 141 host := make(map[string]interface{}) 142 if val, ok := y["host"]; ok { 143 host = val.(map[string]interface{}) 144 } 145 146 hostname := "" 147 if val, ok := host["hostname"]; ok { 148 hostname = val.(string) 149 } 150 151 port := 1247 152 if val, ok := host["port"]; ok { 153 port = val.(int) 154 } 155 156 defaultResource := "" 157 if val, ok := y["default_resource"]; ok { 158 defaultResource = val.(string) 159 } 160 161 // proxy user 162 proxyUser := make(map[string]interface{}) 163 if val, ok := y["proxy_user"]; ok { 164 proxyUser = val.(map[string]interface{}) 165 } 166 167 proxyUsername := "" 168 if val, ok := proxyUser["username"]; ok { 169 proxyUsername = val.(string) 170 } 171 172 proxyPassword := "" 173 if val, ok := proxyUser["password"]; ok { 174 proxyPassword = val.(string) 175 } 176 177 proxyZone := "" 178 if val, ok := proxyUser["zone"]; ok { 179 proxyZone = val.(string) 180 } 181 182 ticket := "" 183 if val, ok := proxyUser["ticket"]; ok { 184 ticket = val.(string) 185 } 186 187 // client user 188 clientUser := make(map[string]interface{}) 189 if val, ok := y["client_user"]; ok { 190 clientUser = val.(map[string]interface{}) 191 } 192 193 clientUsername := "" 194 if val, ok := clientUser["username"]; ok { 195 clientUsername = val.(string) 196 } 197 198 clientZone := "" 199 if val, ok := clientUser["zone"]; ok { 200 clientZone = val.(string) 201 } 202 203 if val, ok := clientUser["ticket"]; ok { 204 ticket = val.(string) 205 } 206 207 // normal user 208 user := make(map[string]interface{}) 209 if val, ok := y["user"]; ok { 210 user = val.(map[string]interface{}) 211 } 212 213 if val, ok := user["username"]; ok { 214 proxyUsername = val.(string) 215 clientUsername = proxyUsername 216 217 } 218 219 if val, ok := user["password"]; ok { 220 proxyPassword = val.(string) 221 } 222 223 if val, ok := user["zone"]; ok { 224 proxyZone = val.(string) 225 clientZone = proxyZone 226 } 227 228 if val, ok := user["ticket"]; ok { 229 ticket = val.(string) 230 } 231 232 // PAM Configuration 233 pamConfig := make(map[string]interface{}) 234 if val, ok := y["pam"]; ok { 235 pamConfig = val.(map[string]interface{}) 236 } 237 238 pamTTL := 0 239 if val, ok := pamConfig["ttl"]; ok { 240 pamTTL = val.(int) 241 } 242 243 // SSL Configuration 244 hasSSLConfig := false 245 sslConfig := make(map[string]interface{}) 246 if val, ok := y["ssl"]; ok { 247 sslConfig = val.(map[string]interface{}) 248 hasSSLConfig = true 249 } 250 251 caCert := "" 252 if val, ok := sslConfig["ca_cert_file"]; ok { 253 caCert = val.(string) 254 } 255 256 keySize := 0 257 if val, ok := sslConfig["key_size"]; ok { 258 keySize = val.(int) 259 } 260 261 algorithm := "" 262 if val, ok := sslConfig["algorithm"]; ok { 263 algorithm = val.(string) 264 } 265 266 saltSize := 0 267 if val, ok := sslConfig["salt_size"]; ok { 268 saltSize = val.(int) 269 } 270 271 hashRounds := 0 272 if val, ok := sslConfig["hash_rounds"]; ok { 273 hashRounds = val.(int) 274 } 275 276 var irodsSSLConfig *IRODSSSLConfig = nil 277 if hasSSLConfig { 278 irodsSSLConfig, err = CreateIRODSSSLConfig(caCert, keySize, algorithm, saltSize, hashRounds) 279 if err != nil { 280 return nil, xerrors.Errorf("failed to create irods ssl config: %w", err) 281 } 282 } 283 284 account := &IRODSAccount{ 285 AuthenticationScheme: authScheme, 286 ClientServerNegotiation: csNegotiation, 287 CSNegotiationPolicy: csNegotiationPolicy, 288 Host: hostname, 289 Port: port, 290 ClientUser: clientUsername, 291 ClientZone: clientZone, 292 ProxyUser: proxyUsername, 293 ProxyZone: proxyZone, 294 Password: proxyPassword, 295 Ticket: ticket, 296 DefaultResource: defaultResource, 297 PamTTL: pamTTL, 298 SSLConfiguration: irodsSSLConfig, 299 } 300 301 account.FixAuthConfiguration() 302 303 return account, nil 304 } 305 306 // SetSSLConfiguration sets SSL Configuration 307 func (account *IRODSAccount) SetSSLConfiguration(sslConf *IRODSSSLConfig) { 308 account.SSLConfiguration = sslConf 309 } 310 311 // SetCSNegotiation sets CSNegotiation policy 312 func (account *IRODSAccount) SetCSNegotiation(requireNegotiation bool, requirePolicy CSNegotiationRequire) { 313 account.ClientServerNegotiation = requireNegotiation 314 account.CSNegotiationPolicy = requirePolicy 315 316 account.FixAuthConfiguration() 317 } 318 319 // UseProxyAccess returns whether it uses proxy access or not 320 func (account *IRODSAccount) UseProxyAccess() bool { 321 return len(account.ProxyUser) > 0 && len(account.ClientUser) > 0 && account.ProxyUser != account.ClientUser 322 } 323 324 // UseTicket returns whether it uses ticket for access control 325 func (account *IRODSAccount) UseTicket() bool { 326 return len(account.Ticket) > 0 327 } 328 329 // MaskSensitiveData returns IRODSAccount object with sensitive data masked 330 func (account *IRODSAccount) MaskSensitiveData() *IRODSAccount { 331 maskedAccount := *account 332 maskedAccount.Password = "<password masked>" 333 maskedAccount.Ticket = "<ticket masked>" 334 return &maskedAccount 335 } 336 337 // Validate validates iRODS account 338 func (account *IRODSAccount) Validate() error { 339 if len(account.Host) == 0 { 340 return xerrors.Errorf("empty host") 341 } 342 343 if account.Port <= 0 { 344 return xerrors.Errorf("empty port") 345 } 346 347 if len(account.ProxyUser) == 0 { 348 return xerrors.Errorf("empty user") 349 } 350 351 err := account.validateUsername(account.ProxyUser) 352 if err != nil { 353 return xerrors.Errorf("failed to validate username %s: %w", account.ProxyUser, err) 354 } 355 356 if len(account.ClientUser) > 0 { 357 err = account.validateUsername(account.ClientUser) 358 if err != nil { 359 return xerrors.Errorf("failed to validate username %s: %w", account.ProxyUser, err) 360 } 361 } 362 363 if len(account.ProxyZone) == 0 { 364 return xerrors.Errorf("empty zone") 365 } 366 367 if len(account.AuthenticationScheme) == 0 { 368 return xerrors.Errorf("empty authentication scheme") 369 } 370 371 if account.AuthenticationScheme != AuthSchemeNative && account.CSNegotiationPolicy != CSNegotiationRequireSSL { 372 return xerrors.Errorf("SSL is required for non-native authentication scheme") 373 } 374 375 if account.CSNegotiationPolicy == CSNegotiationRequireSSL && !account.ClientServerNegotiation { 376 return xerrors.Errorf("client-server negotiation is required for SSL") 377 } 378 379 if account.CSNegotiationPolicy == CSNegotiationRequireSSL && account.SSLConfiguration == nil { 380 return xerrors.Errorf("SSL configuration is empty") 381 } 382 383 return nil 384 } 385 386 func (account *IRODSAccount) validateUsername(username string) error { 387 if len(username) >= common.MaxNameLength { 388 return xerrors.Errorf("username too long") 389 } 390 391 if username == "." || username == ".." { 392 return xerrors.Errorf("invalid username") 393 } 394 395 usernameRegEx, err := regexp.Compile(UsernameRegexString) 396 if err != nil { 397 return xerrors.Errorf("failed to compile regex: %w", err) 398 } 399 400 if !usernameRegEx.Match([]byte(username)) { 401 return xerrors.Errorf("invalid username, containing invalid chars") 402 } 403 return nil 404 } 405 406 func (account *IRODSAccount) FixAuthConfiguration() { 407 if account.AuthenticationScheme != AuthSchemeNative { 408 account.CSNegotiationPolicy = CSNegotiationRequireSSL 409 } 410 411 if account.CSNegotiationPolicy == CSNegotiationRequireSSL { 412 account.ClientServerNegotiation = true 413 } 414 } 415 416 func (account *IRODSAccount) GetRedacted() *IRODSAccount { 417 account2 := IRODSAccount{} 418 account2 = *account 419 account2.Password = "<Redacted>" 420 return &account2 421 }