1/*
2Copyright 2017 WALLIX
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package awsservices
18
19import (
20	"regexp"
21	"strings"
22	"sync"
23
24	awssdk "github.com/aws/aws-sdk-go/aws"
25	"github.com/aws/aws-sdk-go/service/iam"
26	"github.com/wallix/awless/cloud"
27)
28
29func GetCloudServicesForAPIs(apis ...string) (services []cloud.Service) {
30	unique := make(map[string]struct{})
31	for _, api := range apis {
32		if name, ok := ServicePerAPI[api]; ok {
33			if service, exists := cloud.ServiceRegistry[name]; exists {
34				if _, done := unique[name]; !done {
35					unique[name] = struct{}{}
36					services = append(services, service)
37				}
38			}
39		}
40	}
41	return
42}
43
44func GetCloudServicesForTypes(types ...string) (services []cloud.Service) {
45	unique := make(map[string]struct{})
46	for _, typ := range types {
47		if name, ok := ServicePerResourceType[typ]; ok {
48			if service, exists := cloud.ServiceRegistry[name]; exists {
49				if _, done := unique[name]; !done {
50					unique[name] = struct{}{}
51					services = append(services, service)
52				}
53			}
54		}
55	}
56	return
57}
58
59func ResourceTypesPerServiceName() map[string][]string {
60	out := make(map[string][]string)
61	for rT, s := range ServicePerResourceType {
62		out[s] = append(out[s], rT)
63	}
64	return out
65}
66
67var arnResourceInfoRegex = regexp.MustCompile(`(root)|([\w-.]*)/([\w-./]*)`)
68
69type Identity struct {
70	Account, Arn, UserId, ResourceType, ResourcePath, Resource string
71}
72
73func (i *Identity) IsRoot() bool {
74	return i.Resource == "root"
75}
76
77func (i *Identity) IsUserType() bool {
78	return i.ResourceType == "user"
79}
80
81func (s *Access) GetIdentity() (*Identity, error) {
82	resp, err := s.STSAPI.GetCallerIdentity(nil)
83	if err != nil {
84		return nil, err
85	}
86
87	ident := &Identity{
88		Account: awssdk.StringValue(resp.Account),
89		Arn:     awssdk.StringValue(resp.Arn),
90		UserId:  awssdk.StringValue(resp.UserId),
91	}
92
93	splits := strings.Split(ident.Arn, ":")
94	if l := len(splits); l > 0 {
95		ident.ResourcePath = splits[l-1]
96		matches := arnResourceInfoRegex.FindStringSubmatch(ident.ResourcePath)
97		if len(matches) == 4 {
98			if matches[1] == "root" {
99				ident.Resource = "root"
100				ident.ResourceType = "user"
101			} else {
102				ident.ResourceType = matches[2]
103				ident.Resource = matches[3]
104			}
105		}
106	}
107
108	return ident, nil
109}
110
111type UserPolicies struct {
112	Username string
113	Inlined  []string
114	Attached []string
115	ByGroup  map[string][]string
116}
117
118func (s *Access) GetUserPolicies(username string) (*UserPolicies, error) {
119	var wg sync.WaitGroup
120
121	all := &UserPolicies{
122		Username: username,
123		ByGroup:  make(map[string][]string),
124	}
125
126	errc := make(chan error, 4)
127
128	wg.Add(1)
129	go func() {
130		defer wg.Done()
131		policies, err := s.ListUserPolicies(&iam.ListUserPoliciesInput{
132			UserName: awssdk.String(username),
133		})
134		if err != nil {
135			errc <- err
136			return
137		}
138
139		for _, name := range policies.PolicyNames {
140			all.Inlined = append(all.Inlined, awssdk.StringValue(name))
141		}
142	}()
143
144	wg.Add(1)
145	go func() {
146		defer wg.Done()
147		attached, err := s.ListAttachedUserPolicies(&iam.ListAttachedUserPoliciesInput{
148			UserName: awssdk.String(username),
149		})
150		if err != nil {
151			errc <- err
152			return
153		}
154
155		for _, pol := range attached.AttachedPolicies {
156			all.Attached = append(all.Attached, awssdk.StringValue(pol.PolicyName))
157		}
158	}()
159
160	wg.Add(1)
161	go func() {
162		defer wg.Done()
163		groups, err := s.ListGroupsForUser(&iam.ListGroupsForUserInput{
164			UserName: awssdk.String(username),
165		})
166		if err != nil {
167			errc <- err
168			return
169		}
170
171		type result struct {
172			group, policy string
173		}
174		resultC := make(chan result)
175		var wgg sync.WaitGroup
176		for _, group := range groups.Groups {
177			wgg.Add(1)
178			go func(name string) {
179				defer wgg.Done()
180
181				output, err := s.ListAttachedGroupPolicies(&iam.ListAttachedGroupPoliciesInput{
182					GroupName: awssdk.String(name),
183				})
184				if err != nil {
185					errc <- err
186					return
187				}
188				for _, pol := range output.AttachedPolicies {
189					resultC <- result{group: name, policy: awssdk.StringValue(pol.PolicyName)}
190				}
191			}(awssdk.StringValue(group.GroupName))
192		}
193
194		go func() {
195			wgg.Wait()
196			close(resultC)
197		}()
198
199		for res := range resultC {
200			all.ByGroup[res.group] = append(all.ByGroup[res.group], res.policy)
201		}
202	}()
203
204	go func() {
205		wg.Wait()
206		close(errc)
207	}()
208
209	for e := range errc {
210		return all, e
211	}
212
213	return all, nil
214}
215