// flushGlobalPorts opens and closes global ports in the environment. // It keeps a reference count for ports so that only 0-to-1 and 1-to-0 events // modify the environment. func (fw *Firewaller) flushGlobalPorts(rawOpen, rawClose []network.PortRange) error { // Filter which ports are really to open or close. var toOpen, toClose []network.PortRange for _, portRange := range rawOpen { if fw.globalPortRef[portRange] == 0 { toOpen = append(toOpen, portRange) } fw.globalPortRef[portRange]++ } for _, portRange := range rawClose { fw.globalPortRef[portRange]-- if fw.globalPortRef[portRange] == 0 { toClose = append(toClose, portRange) delete(fw.globalPortRef, portRange) } } // Open and close the ports. if len(toOpen) > 0 { if err := fw.environ.OpenPorts(toOpen); err != nil { // TODO(mue) Add local retry logic. return err } network.SortPortRanges(toOpen) logger.Infof("opened port ranges %v in environment", toOpen) } if len(toClose) > 0 { if err := fw.environ.ClosePorts(toClose); err != nil { // TODO(mue) Add local retry logic. return err } network.SortPortRanges(toClose) logger.Infof("closed port ranges %v in environment", toClose) } return nil }
// reconcileInstances compares the initially started watcher for machines, // units and services with the opened and closed ports of the instances and // opens and closes the appropriate ports for each instance. func (fw *Firewaller) reconcileInstances() error { for _, machined := range fw.machineds { m, err := machined.machine() if params.IsCodeNotFound(err) { if err := fw.forgetMachine(machined); err != nil { return err } continue } if err != nil { return err } instanceId, err := m.InstanceId() if errors.IsNotProvisioned(err) { logger.Errorf("Machine not yet provisioned: %v", err) continue } if err != nil { return err } instances, err := fw.environ.Instances([]instance.Id{instanceId}) if err == environs.ErrNoInstances { return nil } if err != nil { return err } machineId := machined.tag.Id() initialPortRanges, err := instances[0].Ports(machineId) if err != nil { return err } // Check which ports to open or to close. toOpen := diffRanges(machined.openedPorts, initialPortRanges) toClose := diffRanges(initialPortRanges, machined.openedPorts) if len(toOpen) > 0 { logger.Infof("opening instance port ranges %v for %q", toOpen, machined.tag) if err := instances[0].OpenPorts(machineId, toOpen); err != nil { // TODO(mue) Add local retry logic. return err } network.SortPortRanges(toOpen) } if len(toClose) > 0 { logger.Infof("closing instance port ranges %v for %q", toClose, machined.tag) if err := instances[0].ClosePorts(machineId, toClose); err != nil { // TODO(mue) Add local retry logic. return err } network.SortPortRanges(toClose) } } return nil }
// convertEndpointsToPorts converts a slice of gwacl.InputEndpoint into a slice of network.PortRange. func convertEndpointsToPortRanges(endpoints []gwacl.InputEndpoint) []network.PortRange { // group ports by prefix on the endpoint name portSets := make(map[string][]network.Port) otherPorts := []network.Port{} for _, endpoint := range endpoints { port := network.Port{ Protocol: strings.ToLower(endpoint.Protocol), Number: endpoint.Port, } if strings.Contains(endpoint.Name, "_range_") { prefix := strings.Split(endpoint.Name, "_range_")[0] portSets[prefix] = append(portSets[prefix], port) } else { otherPorts = append(otherPorts, port) } } portRanges := []network.PortRange{} // convert port sets into port ranges for _, ports := range portSets { portRanges = append(portRanges, network.CollapsePorts(ports)...) } portRanges = append(portRanges, network.CollapsePorts(otherPorts)...) network.SortPortRanges(portRanges) return portRanges }
func makeRanges(stringRanges ...string) []network.PortRange { var results []network.PortRange for _, s := range stringRanges { if strings.Contains(s, "-") { parts := strings.Split(s, "-") fromPort, _ := strconv.Atoi(parts[0]) parts = strings.Split(parts[1], "/") toPort, _ := strconv.Atoi(parts[0]) proto := parts[1] results = append(results, network.PortRange{ FromPort: fromPort, ToPort: toPort, Protocol: proto, }) } else { parts := strings.Split(s, "/") port, _ := strconv.Atoi(parts[0]) proto := parts[1] results = append(results, network.PortRange{ FromPort: port, ToPort: port, Protocol: proto, }) } } network.SortPortRanges(results) return results }
// Helper method to get port from the given firewall rules func getPorts(envName string, rules []cloudapi.FirewallRule) []network.PortRange { portRanges := []network.PortRange{} for _, r := range rules { rule := r.Rule if r.Enabled && strings.HasPrefix(rule, "FROM tag "+envName) && strings.Contains(rule, "PORT") { if firewallSinglePortRule.MatchString(rule) { parts := firewallSinglePortRule.FindStringSubmatch(rule) if len(parts) != 3 { continue } protocol := parts[1] n, _ := strconv.Atoi(parts[2]) portRanges = append(portRanges, network.PortRange{Protocol: protocol, FromPort: n, ToPort: n}) } else if firewallMultiPortRule.MatchString(rule) { parts := firewallMultiPortRule.FindStringSubmatch(rule) if len(parts) != 3 { continue } protocol := parts[1] ports := []network.Port{} portStrings := strings.Split(parts[2], " AND ") for _, portString := range portStrings { portString = portString[strings.LastIndex(portString, "PORT")+5:] port, _ := strconv.Atoi(portString) ports = append(ports, network.Port{protocol, port}) } portRanges = append(portRanges, network.CollapsePorts(ports)...) } } } network.SortPortRanges(portRanges) return portRanges }
// AddPorts adds the specified port range. func (ni *NetworkInterface) AddPorts(protocol string, from, to int) { ni.Ports = append(ni.Ports, network.PortRange{ Protocol: protocol, FromPort: from, ToPort: to, }) network.SortPortRanges(ni.Ports) }
// Ports is specified in the Instance interface. func (azInstance *azureInstance) Ports(machineId string) (ports []network.PortRange, err error) { err = azInstance.apiCall(false, func(api *gwacl.ManagementAPI) error { ports, err = azInstance.listPorts(api) return err }) if ports != nil { network.SortPortRanges(ports) } return ports, err }
// flushInstancePorts opens and closes ports global on the machine. func (fw *Firewaller) flushInstancePorts(machined *machineData, toOpen, toClose []network.PortRange) error { // If there's nothing to do, do nothing. // This is important because when a machine is first created, // it will have no instance id but also no open ports - // InstanceId will fail but we don't care. if len(toOpen) == 0 && len(toClose) == 0 { return nil } m, err := machined.machine() if params.IsCodeNotFound(err) { return nil } if err != nil { return err } machineId := machined.tag.Id() instanceId, err := m.InstanceId() if err != nil { return err } instances, err := fw.environ.Instances([]instance.Id{instanceId}) if err != nil { return err } // Open and close the ports. if len(toOpen) > 0 { if err := instances[0].OpenPorts(machineId, toOpen); err != nil { // TODO(mue) Add local retry logic. return err } network.SortPortRanges(toOpen) logger.Infof("opened port ranges %v on %q", toOpen, machined.tag) } if len(toClose) > 0 { if err := instances[0].ClosePorts(machineId, toClose); err != nil { // TODO(mue) Add local retry logic. return err } network.SortPortRanges(toClose) logger.Infof("closed port ranges %v on %q", toClose, machined.tag) } return nil }
func (ctx *HookContext) OpenedPorts() []network.PortRange { var unitRanges []network.PortRange for portRange, relUnit := range ctx.machinePorts { if relUnit.Unit == ctx.unit.Tag().String() { unitRanges = append(unitRanges, portRange) } } network.SortPortRanges(unitRanges) return unitRanges }
// reconcileGlobal compares the initially started watcher for machines, // units and services with the opened and closed ports globally and // opens and closes the appropriate ports for the whole environment. func (fw *Firewaller) reconcileGlobal() error { initialPortRanges, err := fw.environ.Ports() if err != nil { return err } collector := make(map[network.PortRange]bool) for _, machined := range fw.machineds { for portRange, unitTag := range machined.definedPorts { unitd, known := machined.unitds[unitTag] if !known { delete(machined.unitds, unitTag) continue } if unitd.serviced.exposed { collector[portRange] = true } } } wantedPorts := []network.PortRange{} for port := range collector { wantedPorts = append(wantedPorts, port) } // Check which ports to open or to close. toOpen := diffRanges(wantedPorts, initialPortRanges) toClose := diffRanges(initialPortRanges, wantedPorts) if len(toOpen) > 0 { logger.Infof("opening global ports %v", toOpen) if err := fw.environ.OpenPorts(toOpen); err != nil { return err } network.SortPortRanges(toOpen) } if len(toClose) > 0 { logger.Infof("closing global ports %v", toClose) if err := fw.environ.ClosePorts(toClose); err != nil { return err } network.SortPortRanges(toClose) } return nil }
// GetMachinePorts returns the port ranges opened on a machine for the specified // subnet as a map mapping port ranges to the tags of the units that opened // them. func (f *FirewallerAPI) GetMachinePorts(args params.MachinePortsParams) (params.MachinePortsResults, error) { result := params.MachinePortsResults{ Results: make([]params.MachinePortsResult, len(args.Params)), } canAccess, err := f.accessMachine() if err != nil { return params.MachinePortsResults{}, err } for i, param := range args.Params { machineTag, err := names.ParseMachineTag(param.MachineTag) if err != nil { result.Results[i].Error = common.ServerError(err) continue } var subnetTag names.SubnetTag if param.SubnetTag != "" { subnetTag, err = names.ParseSubnetTag(param.SubnetTag) if err != nil { result.Results[i].Error = common.ServerError(err) continue } } machine, err := f.getMachine(canAccess, machineTag) if err != nil { result.Results[i].Error = common.ServerError(err) continue } ports, err := machine.OpenedPorts(subnetTag.Id()) if err != nil { result.Results[i].Error = common.ServerError(err) continue } if ports != nil { portRangeMap := ports.AllPortRanges() var portRanges []network.PortRange for portRange := range portRangeMap { portRanges = append(portRanges, portRange) } network.SortPortRanges(portRanges) for _, portRange := range portRanges { unitTag := names.NewUnitTag(portRangeMap[portRange]).String() result.Results[i].Ports = append(result.Results[i].Ports, params.MachinePortRange{ UnitTag: unitTag, PortRange: params.FromNetworkPortRange(portRange), }) } } } return result, nil }
// assertPorts retrieves the open ports of the instance and compares them // to the expected. func (s *FirewallerSuite) assertPorts(c *gc.C, inst instance.Instance, machineId string, expected []network.PortRange) { s.BackingState.StartSync() start := time.Now() for { got, err := inst.Ports(machineId) if err != nil { c.Fatal(err) return } network.SortPortRanges(got) network.SortPortRanges(expected) if reflect.DeepEqual(got, expected) { c.Succeed() return } if time.Since(start) > coretesting.LongWait { c.Fatalf("timed out: expected %q; got %q", expected, got) return } time.Sleep(coretesting.ShortWait) } }
// assertEnvironPorts retrieves the open ports of environment and compares them // to the expected. func (s *firewallerBaseSuite) assertEnvironPorts(c *gc.C, expected []network.PortRange) { s.BackingState.StartSync() start := time.Now() for { got, err := s.Environ.Ports() if err != nil { c.Fatal(err) return } network.SortPortRanges(got) network.SortPortRanges(expected) if reflect.DeepEqual(got, expected) { c.Succeed() return } if time.Since(start) > coretesting.LongWait { c.Fatalf("timed out: expected %q; got %q", expected, got) return } time.Sleep(coretesting.ShortWait) } }
// RemovePorts removes the specified port range. func (ni *NetworkInterface) RemovePorts(protocol string, from, to int) { portRange := network.PortRange{ Protocol: protocol, FromPort: from, ToPort: to, } for i, port := range ni.Ports { if port == portRange { ni.Ports = append(ni.Ports[:i], ni.Ports[i+1:]...) break } } network.SortPortRanges(ni.Ports) }
func (*PortSuite) TestSortPortRanges(c *gc.C) { ranges := []network.PortRange{ {10, 100, "udp"}, {80, 90, "tcp"}, {80, 80, "tcp"}, } expected := []network.PortRange{ {80, 80, "tcp"}, {80, 90, "tcp"}, {10, 100, "udp"}, } network.SortPortRanges(ranges) c.Assert(ranges, gc.DeepEquals, expected) }
func (c *defaultFirewaller) portsInGroup(name string) (portRanges []network.PortRange, err error) { group, err := c.environ.nova().SecurityGroupByName(name) if err != nil { return nil, err } for _, p := range (*group).Rules { portRanges = append(portRanges, network.PortRange{ Protocol: *p.IPProtocol, FromPort: *p.FromPort, ToPort: *p.ToPort, }) } network.SortPortRanges(portRanges) return portRanges, nil }
func (c *defaultFirewaller) portsInGroup(nameRegexp string) (portRanges []network.PortRange, err error) { group, err := c.matchingGroup(nameRegexp) if err != nil { return nil, err } for _, p := range group.Rules { portRanges = append(portRanges, network.PortRange{ Protocol: *p.IPProtocol, FromPort: *p.FromPort, ToPort: *p.ToPort, }) } network.SortPortRanges(portRanges) return portRanges, nil }
func (e *environ) Ports() (ports []network.PortRange, err error) { if mode := e.ecfg().FirewallMode(); mode != config.FwGlobal { return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from environment", mode) } estate, err := e.state() if err != nil { return nil, err } estate.mu.Lock() defer estate.mu.Unlock() for p := range estate.globalPorts { ports = append(ports, p) } network.SortPortRanges(ports) return }
func (inst *dummyInstance) Ports(machineId string) (ports []network.PortRange, err error) { defer delay() if inst.firewallMode != config.FwInstance { return nil, fmt.Errorf("invalid firewall mode %q for retrieving ports from instance", inst.firewallMode) } if inst.machineId != machineId { panic(fmt.Errorf("Ports with mismatched machine id, expected %q got %q", inst.machineId, machineId)) } inst.state.mu.Lock() defer inst.state.mu.Unlock() for p := range inst.ports { ports = append(ports, p) } network.SortPortRanges(ports) return }
func (s *instanceSuite) testPorts(c *gc.C, maskStateServerPorts bool) { // Update the role's endpoints by hand. configSetNetwork(s.role).InputEndpoints = &[]gwacl.InputEndpoint{{ LocalPort: 223, Protocol: "udp", Name: "test223", Port: 2123, }, { LocalPort: 123, Protocol: "udp", Name: "test123", Port: 1123, }, { LocalPort: 456, Protocol: "tcp", Name: "test456", Port: 4456, }, { LocalPort: s.env.Config().APIPort(), Protocol: "tcp", Name: "apiserver", Port: s.env.Config().APIPort(), }} responses := preparePortChangeConversation(c, s.role) record := gwacl.PatchManagementAPIResponses(responses) s.instance.maskStateServerPorts = maskStateServerPorts ports, err := s.instance.Ports("machine-id") c.Assert(err, jc.ErrorIsNil) assertPortChangeConversation(c, *record, []expectedRequest{ {"GET", ".*/deployments/deployment-one/roles/role-one"}, // GetRole }) expected := []network.PortRange{ {4456, 4456, "tcp"}, {1123, 1123, "udp"}, {2123, 2123, "udp"}, } if !maskStateServerPorts { expected = append(expected, network.PortRange{s.env.Config().APIPort(), s.env.Config().APIPort(), "tcp"}) network.SortPortRanges(expected) } c.Check(ports, gc.DeepEquals, expected) }
func (s *instanceSuite) TestConvertAndFilterEndpoints(c *gc.C) { endpoints := []gwacl.InputEndpoint{ { LocalPort: 123, Protocol: "udp", Name: "test123", Port: 1123, }, { LocalPort: 456, Protocol: "tcp", Name: "test456", Port: 44, }} endpoints = append(endpoints, s.env.getInitialEndpoints(true)...) expectedPorts := []network.PortRange{ {1123, 1123, "udp"}, {44, 44, "tcp"}} network.SortPortRanges(expectedPorts) c.Check(convertAndFilterEndpoints(endpoints, s.env, true), gc.DeepEquals, expectedPorts) }
func (u *UniterAPIV1) getOneMachinePorts(canAccess common.AuthFunc, machineTag string) params.MachinePortsResult { tag, err := names.ParseMachineTag(machineTag) if err != nil { return params.MachinePortsResult{Error: common.ServerError(common.ErrPerm)} } if !canAccess(tag) { return params.MachinePortsResult{Error: common.ServerError(common.ErrPerm)} } machine, err := u.getMachine(tag) if err != nil { return params.MachinePortsResult{Error: common.ServerError(err)} } allPorts, err := machine.AllPorts() if err != nil { return params.MachinePortsResult{Error: common.ServerError(err)} } var resultPorts []params.MachinePortRange for _, ports := range allPorts { // AllPortRanges gives a map, but apis require a stable order // for results, so sort the port ranges. portRangesToUnits := ports.AllPortRanges() portRanges := make([]network.PortRange, 0, len(portRangesToUnits)) for portRange := range portRangesToUnits { portRanges = append(portRanges, portRange) } network.SortPortRanges(portRanges) for _, portRange := range portRanges { unitName := portRangesToUnits[portRange] resultPorts = append(resultPorts, params.MachinePortRange{ UnitTag: names.NewUnitTag(unitName).String(), PortRange: params.FromNetworkPortRange(portRange), }) } } return params.MachinePortsResult{ Ports: resultPorts, } }
func (s *OpenedPortsSuite) TestRunAllFormats(c *gc.C) { expectedPorts := []network.PortRange{ {10, 20, "tcp"}, {80, 80, "tcp"}, {53, 55, "udp"}, {63, 63, "udp"}, } network.SortPortRanges(expectedPorts) portsAsStrings := make([]string, len(expectedPorts)) for i, portRange := range expectedPorts { portsAsStrings[i] = portRange.String() } defaultOutput := strings.Join(portsAsStrings, "\n") + "\n" jsonOutput := `["` + strings.Join(portsAsStrings, `","`) + `"]` + "\n" yamlOutput := "- " + strings.Join(portsAsStrings, "\n- ") + "\n" formatToOutput := map[string]string{ "": defaultOutput, "smart": defaultOutput, "json": jsonOutput, "yaml": yamlOutput, } for format, expectedOutput := range formatToOutput { hctx := s.getContextAndOpenPorts(c) stdout := "" stderr := "" if format == "" { stdout, stderr = s.runCommand(c, hctx) } else { stdout, stderr = s.runCommand(c, hctx, "--format", format) } c.Check(stdout, gc.Equals, expectedOutput) c.Check(stderr, gc.Equals, "") hctx.info.CheckPorts(c, expectedPorts) } }