forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 1
/
groupsyncer.go
191 lines (164 loc) · 6.19 KB
/
groupsyncer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package syncgroups
import (
"fmt"
"io"
"net"
"time"
"github.com/golang/glog"
"gopkg.in/ldap.v2"
kapierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/openshift/origin/pkg/auth/ldaputil"
"github.com/openshift/origin/pkg/client"
"github.com/openshift/origin/pkg/oc/admin/groups/sync/interfaces"
userapi "github.com/openshift/origin/pkg/user/apis/user"
)
// GroupSyncer runs a Sync job on Groups
type GroupSyncer interface {
// Sync syncs groups in OpenShift with records from an external source
Sync() (groupsAffected []*userapi.Group, errors []error)
}
// LDAPGroupSyncer sync Groups with records on an external LDAP server
type LDAPGroupSyncer struct {
// Lists all groups to be synced
GroupLister interfaces.LDAPGroupLister
// Fetches a group and extracts object metainformation and membership list from a group
GroupMemberExtractor interfaces.LDAPMemberExtractor
// Maps an LDAP user entry to an OpenShift User's Name
UserNameMapper interfaces.LDAPUserNameMapper
// Maps an LDAP group enrty to an OpenShift Group's Name
GroupNameMapper interfaces.LDAPGroupNameMapper
// Allows the Syncer to search for OpenShift Groups
GroupClient client.GroupInterface
// Host stores the address:port of the LDAP server
Host string
// DryRun indicates that no changes should be made.
DryRun bool
// Out is used to provide output while the sync job is happening
Out io.Writer
Err io.Writer
}
var _ GroupSyncer = &LDAPGroupSyncer{}
// Sync allows the LDAPGroupSyncer to be a GroupSyncer
func (s *LDAPGroupSyncer) Sync() ([]*userapi.Group, []error) {
openshiftGroups := []*userapi.Group{}
var errors []error
// determine what to sync
glog.V(1).Infof("Listing with %v", s.GroupLister)
ldapGroupUIDs, err := s.GroupLister.ListGroups()
if err != nil {
errors = append(errors, err)
return nil, errors
}
glog.V(1).Infof("Sync ldapGroupUIDs %v", ldapGroupUIDs)
for _, ldapGroupUID := range ldapGroupUIDs {
glog.V(1).Infof("Checking LDAP group %v", ldapGroupUID)
// get membership data
memberEntries, err := s.GroupMemberExtractor.ExtractMembers(ldapGroupUID)
if err != nil {
fmt.Fprintf(s.Err, "Error determining LDAP group membership for %q: %v.\n", ldapGroupUID, err)
errors = append(errors, err)
continue
}
// determine OpenShift Users' usernames for LDAP group members
usernames, err := s.determineUsernames(memberEntries)
if err != nil {
fmt.Fprintf(s.Err, "Error determining usernames for LDAP group %q: %v.\n", ldapGroupUID, err)
errors = append(errors, err)
continue
}
glog.V(1).Infof("Has OpenShift users %v", usernames)
// update the OpenShift Group corresponding to this record
openshiftGroup, err := s.makeOpenShiftGroup(ldapGroupUID, usernames)
if err != nil {
fmt.Fprintf(s.Err, "Error building OpenShift group for LDAP group %q: %v.\n", ldapGroupUID, err)
errors = append(errors, err)
continue
}
openshiftGroups = append(openshiftGroups, openshiftGroup)
if !s.DryRun {
fmt.Fprintf(s.Out, "group/%s\n", openshiftGroup.Name)
if err := s.updateOpenShiftGroup(openshiftGroup); err != nil {
fmt.Fprintf(s.Err, "Error updating OpenShift group %q for LDAP group %q: %v.\n", openshiftGroup.Name, ldapGroupUID, err)
errors = append(errors, err)
continue
}
}
}
return openshiftGroups, errors
}
// determineUsers determines the OpenShift Users that correspond to a list of LDAP member entries
func (s *LDAPGroupSyncer) determineUsernames(members []*ldap.Entry) ([]string, error) {
var usernames []string
for _, member := range members {
username, err := s.UserNameMapper.UserNameFor(member)
if err != nil {
return nil, err
}
glog.V(2).Infof("Found OpenShift username %q for LDAP user for %v", username, member)
usernames = append(usernames, username)
}
return usernames, nil
}
// updateOpenShiftGroup creates the OpenShift Group in etcd
func (s *LDAPGroupSyncer) updateOpenShiftGroup(openshiftGroup *userapi.Group) error {
if len(openshiftGroup.UID) > 0 {
_, err := s.GroupClient.Update(openshiftGroup)
return err
}
_, err := s.GroupClient.Create(openshiftGroup)
return err
}
// makeOpenShiftGroup creates the OpenShift Group object that needs to be updated, updates its data
func (s *LDAPGroupSyncer) makeOpenShiftGroup(ldapGroupUID string, usernames []string) (*userapi.Group, error) {
hostIP, _, err := net.SplitHostPort(s.Host)
if err != nil {
return nil, err
}
groupName, err := s.GroupNameMapper.GroupNameFor(ldapGroupUID)
if err != nil {
return nil, err
}
group, err := s.GroupClient.Get(groupName, metav1.GetOptions{})
if kapierrors.IsNotFound(err) {
group = &userapi.Group{}
group.Name = groupName
group.Annotations = map[string]string{
ldaputil.LDAPURLAnnotation: s.Host,
ldaputil.LDAPUIDAnnotation: ldapGroupUID,
}
group.Labels = map[string]string{
ldaputil.LDAPHostLabel: hostIP,
}
} else if err != nil {
return nil, err
}
// make sure we aren't taking over an OpenShift group that is already related to a different LDAP group
if host, exists := group.Labels[ldaputil.LDAPHostLabel]; !exists || (host != hostIP) {
return nil, fmt.Errorf("group %q: %s label did not match sync host: wanted %s, got %s",
group.Name, ldaputil.LDAPHostLabel, hostIP, host)
}
if url, exists := group.Annotations[ldaputil.LDAPURLAnnotation]; !exists || (url != s.Host) {
return nil, fmt.Errorf("group %q: %s annotation did not match sync host: wanted %s, got %s",
group.Name, ldaputil.LDAPURLAnnotation, s.Host, url)
}
if uid, exists := group.Annotations[ldaputil.LDAPUIDAnnotation]; !exists || (uid != ldapGroupUID) {
return nil, fmt.Errorf("group %q: %s annotation did not match LDAP UID: wanted %s, got %s",
group.Name, ldaputil.LDAPUIDAnnotation, ldapGroupUID, uid)
}
// overwrite Group Users data
group.Users = usernames
group.Annotations[ldaputil.LDAPSyncTimeAnnotation] = ISO8601(time.Now())
return group, nil
}
// ISO8601 returns an ISO 6801 formatted string from a time.
func ISO8601(t time.Time) string {
var tz string
if zone, offset := t.Zone(); zone == "UTC" {
tz = "Z"
} else {
tz = fmt.Sprintf("%03d00", offset/3600)
}
return fmt.Sprintf("%04d-%02d-%02dT%02d:%02d:%02d%s",
t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), tz)
}