github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/openstack/legacy_firewaller.go (about) 1 // Copyright 2015-2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package openstack 5 6 import ( 7 "fmt" 8 "regexp" 9 10 "github.com/juju/clock" 11 "github.com/juju/errors" 12 gooseerrors "gopkg.in/goose.v2/errors" 13 "gopkg.in/goose.v2/neutron" 14 "gopkg.in/goose.v2/nova" 15 16 corenetwork "github.com/juju/juju/core/network" 17 "github.com/juju/juju/environs/config" 18 "github.com/juju/juju/environs/context" 19 "github.com/juju/juju/environs/instances" 20 "github.com/juju/juju/network" 21 "github.com/juju/juju/provider/common" 22 ) 23 24 type legacyNovaFirewaller struct { 25 firewallerBase 26 } 27 28 // SetUpGroups creates the security groups for the new machine, and 29 // returns them. 30 // 31 // Instances are tagged with a group so they can be distinguished from 32 // other instances that might be running on the same OpenStack account. 33 // In addition, a specific machine security group is created for each 34 // machine, so that its firewall rules can be configured per machine. 35 func (c *legacyNovaFirewaller) SetUpGroups(ctx context.ProviderCallContext, controllerUUID, machineId string, apiPort int) ([]string, error) { 36 jujuGroup, err := c.setUpGlobalGroup(ctx, c.jujuGroupName(controllerUUID), apiPort) 37 if err != nil { 38 return nil, errors.Trace(err) 39 } 40 var machineGroup nova.SecurityGroup 41 switch c.environ.Config().FirewallMode() { 42 case config.FwInstance: 43 machineGroup, err = c.ensureGroup(ctx, c.machineGroupName(controllerUUID, machineId), nil) 44 case config.FwGlobal: 45 machineGroup, err = c.ensureGroup(ctx, c.globalGroupName(controllerUUID), nil) 46 } 47 if err != nil { 48 return nil, errors.Trace(err) 49 } 50 groupNames := []string{jujuGroup.Name, machineGroup.Name} 51 if c.environ.ecfg().useDefaultSecurityGroup() { 52 groupNames = append(groupNames, "default") 53 } 54 return groupNames, nil 55 } 56 57 func (c *legacyNovaFirewaller) setUpGlobalGroup(ctx context.ProviderCallContext, groupName string, apiPort int) (nova.SecurityGroup, error) { 58 return c.ensureGroup(ctx, groupName, 59 []nova.RuleInfo{ 60 { 61 IPProtocol: "tcp", 62 ToPort: 22, 63 FromPort: 22, 64 Cidr: "0.0.0.0/0", 65 }, 66 { 67 IPProtocol: "tcp", 68 ToPort: apiPort, 69 FromPort: apiPort, 70 Cidr: "0.0.0.0/0", 71 }, 72 { 73 IPProtocol: "tcp", 74 FromPort: 1, 75 ToPort: 65535, 76 }, 77 { 78 IPProtocol: "udp", 79 FromPort: 1, 80 ToPort: 65535, 81 }, 82 { 83 IPProtocol: "icmp", 84 FromPort: -1, 85 ToPort: -1, 86 }, 87 }) 88 } 89 90 // legacyZeroGroup holds the zero security group. 91 var legacyZeroGroup nova.SecurityGroup 92 93 // ensureGroup returns the security group with name and perms. 94 // If a group with name does not exist, one will be created. 95 // If it exists, its permissions are set to perms. 96 func (c *legacyNovaFirewaller) ensureGroup(ctx context.ProviderCallContext, name string, rules []nova.RuleInfo) (nova.SecurityGroup, error) { 97 novaClient := c.environ.nova() 98 // First attempt to look up an existing group by name. 99 group, err := novaClient.SecurityGroupByName(name) 100 if err == nil { 101 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 102 // Group exists, so assume it is correctly set up and return it. 103 // TODO(jam): 2013-09-18 http://pad.lv/121795 104 // We really should verify the group is set up correctly, 105 // because deleting and re-creating environments can get us bad 106 // groups (especially if they were set up under Python) 107 return *group, nil 108 } 109 // Doesn't exist, so try and create it. 110 group, err = novaClient.CreateSecurityGroup(name, "juju group") 111 if err != nil { 112 if !gooseerrors.IsDuplicateValue(err) { 113 return legacyZeroGroup, err 114 } else { 115 // We just tried to create a duplicate group, so load the existing group. 116 group, err = novaClient.SecurityGroupByName(name) 117 if err != nil { 118 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 119 return legacyZeroGroup, err 120 } 121 return *group, nil 122 } 123 } 124 // The new group is created so now add the rules. 125 group.Rules = make([]nova.SecurityGroupRule, len(rules)) 126 for i, rule := range rules { 127 rule.ParentGroupId = group.Id 128 if rule.Cidr == "" { 129 // http://pad.lv/1226996 Rules that don't have a CIDR 130 // are meant to apply only to this group. If you don't 131 // supply CIDR or GroupId then openstack assumes you 132 // mean CIDR=0.0.0.0/0 133 rule.GroupId = &group.Id 134 } 135 groupRule, err := novaClient.CreateSecurityGroupRule(rule) 136 if err != nil && !gooseerrors.IsDuplicateValue(err) { 137 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 138 return legacyZeroGroup, err 139 } 140 group.Rules[i] = *groupRule 141 } 142 return *group, nil 143 } 144 145 func (c *legacyNovaFirewaller) deleteSecurityGroups(ctx context.ProviderCallContext, match func(name string) bool) error { 146 novaclient := c.environ.nova() 147 securityGroups, err := novaclient.ListSecurityGroups() 148 if err != nil { 149 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 150 return errors.Annotate(err, "cannot list security groups") 151 } 152 for _, group := range securityGroups { 153 if match(group.Name) { 154 deleteSecurityGroup(ctx, 155 novaclient.DeleteSecurityGroup, 156 group.Name, 157 group.Id, 158 clock.WallClock, 159 ) 160 } 161 } 162 return nil 163 } 164 165 // DeleteAllControllerGroups implements Firewaller interface. 166 func (c *legacyNovaFirewaller) DeleteAllControllerGroups(ctx context.ProviderCallContext, controllerUUID string) error { 167 return deleteSecurityGroupsMatchingName(ctx, c.deleteSecurityGroups, c.jujuControllerGroupPrefix(controllerUUID)) 168 } 169 170 // DeleteAllModelGroups implements Firewaller interface. 171 func (c *legacyNovaFirewaller) DeleteAllModelGroups(ctx context.ProviderCallContext) error { 172 return deleteSecurityGroupsMatchingName(ctx, c.deleteSecurityGroups, c.jujuGroupRegexp()) 173 } 174 175 // DeleteGroups implements Firewaller interface. 176 func (c *legacyNovaFirewaller) DeleteGroups(ctx context.ProviderCallContext, names ...string) error { 177 return deleteSecurityGroupsOneOfNames(ctx, c.deleteSecurityGroups, names...) 178 } 179 180 // UpdateGroupController implements Firewaller interface. 181 func (c *legacyNovaFirewaller) UpdateGroupController(ctx context.ProviderCallContext, controllerUUID string) error { 182 novaClient := c.environ.nova() 183 groups, err := novaClient.ListSecurityGroups() 184 if err != nil { 185 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 186 return errors.Trace(err) 187 } 188 re, err := regexp.Compile(c.jujuGroupRegexp()) 189 if err != nil { 190 return errors.Trace(err) 191 } 192 193 var failed []string 194 for _, group := range groups { 195 if !re.MatchString(group.Name) { 196 continue 197 } 198 err := c.updateGroupControllerUUID(ctx, &group, controllerUUID) 199 if err != nil { 200 logger.Errorf("error updating controller for security group %s: %v", group.Id, err) 201 failed = append(failed, group.Id) 202 if denied := common.MaybeHandleCredentialError(IsAuthorisationFailure, err, ctx); denied { 203 // We will keep failing 100% once the credential is deemed invalid - no point in persisting. 204 break 205 } 206 } 207 } 208 if len(failed) != 0 { 209 return errors.Errorf("errors updating controller for security groups: %v", failed) 210 } 211 return nil 212 } 213 214 func (c *legacyNovaFirewaller) updateGroupControllerUUID(ctx context.ProviderCallContext, group *nova.SecurityGroup, controllerUUID string) error { 215 newName, err := replaceControllerUUID(group.Name, controllerUUID) 216 if err != nil { 217 return errors.Trace(err) 218 } 219 client := c.environ.nova() 220 _, err = client.UpdateSecurityGroup(group.Id, newName, group.Description) 221 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 222 return errors.Trace(err) 223 } 224 225 // OpenPorts implements Firewaller interface. 226 func (c *legacyNovaFirewaller) OpenPorts(ctx context.ProviderCallContext, rules []network.IngressRule) error { 227 return c.openPorts(ctx, c.openPortsInGroup, rules) 228 } 229 230 // ClosePorts implements Firewaller interface. 231 func (c *legacyNovaFirewaller) ClosePorts(ctx context.ProviderCallContext, rules []network.IngressRule) error { 232 return c.closePorts(ctx, c.closePortsInGroup, rules) 233 } 234 235 // IngressRules implements Firewaller interface. 236 func (c *legacyNovaFirewaller) IngressRules(ctx context.ProviderCallContext) ([]network.IngressRule, error) { 237 return c.ingressRules(ctx, c.ingressRulesInGroup) 238 } 239 240 // OpenInstancePorts implements Firewaller interface. 241 func (c *legacyNovaFirewaller) OpenInstancePorts(ctx context.ProviderCallContext, inst instances.Instance, machineId string, rules []network.IngressRule) error { 242 return c.openInstancePorts(ctx, c.openPortsInGroup, machineId, rules) 243 } 244 245 // CloseInstancePorts implements Firewaller interface. 246 func (c *legacyNovaFirewaller) CloseInstancePorts(ctx context.ProviderCallContext, inst instances.Instance, machineId string, rules []network.IngressRule) error { 247 return c.closeInstancePorts(ctx, c.closePortsInGroup, machineId, rules) 248 } 249 250 // InstanceIngressRules implements Firewaller interface. 251 func (c *legacyNovaFirewaller) InstanceIngressRules(ctx context.ProviderCallContext, inst instances.Instance, machineId string) ([]network.IngressRule, error) { 252 return c.instanceIngressRules(ctx, c.ingressRulesInGroup, machineId) 253 } 254 255 func (c *legacyNovaFirewaller) matchingGroup(ctx context.ProviderCallContext, nameRegExp string) (nova.SecurityGroup, error) { 256 re, err := regexp.Compile(nameRegExp) 257 if err != nil { 258 return nova.SecurityGroup{}, err 259 } 260 novaclient := c.environ.nova() 261 allGroups, err := novaclient.ListSecurityGroups() 262 if err != nil { 263 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 264 return nova.SecurityGroup{}, err 265 } 266 var matchingGroups []nova.SecurityGroup 267 for _, group := range allGroups { 268 if re.MatchString(group.Name) { 269 matchingGroups = append(matchingGroups, group) 270 } 271 } 272 numMatching := len(matchingGroups) 273 if numMatching == 0 { 274 return nova.SecurityGroup{}, errors.NotFoundf("security groups matching %q", nameRegExp) 275 } else if numMatching > 1 { 276 return nova.SecurityGroup{}, errors.New(fmt.Sprintf("%d security groups found matching %q, expected 1", numMatching, nameRegExp)) 277 } 278 return matchingGroups[0], nil 279 } 280 281 func (c *legacyNovaFirewaller) openPortsInGroup(ctx context.ProviderCallContext, nameRegExp string, rules []network.IngressRule) error { 282 group, err := c.matchingGroup(ctx, nameRegExp) 283 if err != nil { 284 return errors.Trace(err) 285 } 286 novaclient := c.environ.nova() 287 ruleInfo := rulesToRuleInfo(group.Id, rules) 288 for _, rule := range ruleInfo { 289 _, err := novaclient.CreateSecurityGroupRule(legacyRuleInfo(rule)) 290 if err != nil { 291 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 292 // TODO: if err is not rule already exists, raise? 293 logger.Debugf("error creating security group rule: %v", err.Error()) 294 } 295 } 296 return nil 297 } 298 299 func legacyRuleInfo(in neutron.RuleInfoV2) nova.RuleInfo { 300 return nova.RuleInfo{ 301 ParentGroupId: in.ParentGroupId, 302 FromPort: in.PortRangeMin, 303 ToPort: in.PortRangeMax, 304 IPProtocol: in.IPProtocol, 305 Cidr: in.RemoteIPPrefix, 306 } 307 } 308 309 // ruleMatchesPortRange checks if supplied nova security group rule matches the port range 310 func legacyRuleMatchesPortRange(rule nova.SecurityGroupRule, portRange network.IngressRule) bool { 311 if rule.IPProtocol == nil || rule.FromPort == nil || rule.ToPort == nil { 312 return false 313 } 314 return *rule.IPProtocol == portRange.Protocol && 315 *rule.FromPort == portRange.FromPort && 316 *rule.ToPort == portRange.ToPort 317 } 318 319 func (c *legacyNovaFirewaller) closePortsInGroup(ctx context.ProviderCallContext, nameRegExp string, rules []network.IngressRule) error { 320 if len(rules) == 0 { 321 return nil 322 } 323 group, err := c.matchingGroup(ctx, nameRegExp) 324 if err != nil { 325 return errors.Trace(err) 326 } 327 novaclient := c.environ.nova() 328 for _, portRange := range rules { 329 for _, p := range group.Rules { 330 if !legacyRuleMatchesPortRange(p, portRange) { 331 continue 332 } 333 err := novaclient.DeleteSecurityGroupRule(p.Id) 334 if err != nil { 335 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 336 return errors.Trace(err) 337 } 338 break 339 } 340 } 341 return nil 342 } 343 344 func (c *legacyNovaFirewaller) ingressRulesInGroup(ctx context.ProviderCallContext, nameRegexp string) (rules []network.IngressRule, err error) { 345 group, err := c.matchingGroup(ctx, nameRegexp) 346 if err != nil { 347 return nil, errors.Trace(err) 348 } 349 // Keep track of all the RemoteIPPrefixes for each port range. 350 portSourceCIDRs := make(map[corenetwork.PortRange]*[]string) 351 for _, p := range group.Rules { 352 portRange := corenetwork.PortRange{*p.FromPort, *p.ToPort, *p.IPProtocol} 353 // Record the RemoteIPPrefix for the port range. 354 remotePrefix := p.IPRange["cidr"] 355 if remotePrefix == "" { 356 remotePrefix = "0.0.0.0/0" 357 } 358 sourceCIDRs, ok := portSourceCIDRs[portRange] 359 if !ok { 360 sourceCIDRs = &[]string{} 361 portSourceCIDRs[portRange] = sourceCIDRs 362 } 363 *sourceCIDRs = append(*sourceCIDRs, remotePrefix) 364 } 365 // Combine all the port ranges and remote prefixes. 366 for portRange, sourceCIDRs := range portSourceCIDRs { 367 rule, err := network.NewIngressRule( 368 portRange.Protocol, 369 portRange.FromPort, 370 portRange.ToPort, 371 *sourceCIDRs...) 372 if err != nil { 373 common.HandleCredentialError(IsAuthorisationFailure, err, ctx) 374 return nil, errors.Trace(err) 375 } 376 rules = append(rules, rule) 377 } 378 network.SortIngressRules(rules) 379 return rules, nil 380 }