github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/oracle/network/firewall.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package network
     5  
     6  import (
     7  	"fmt"
     8  	"reflect"
     9  	"sort"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/juju/clock"
    15  	"github.com/juju/errors"
    16  	"github.com/juju/go-oracle-cloud/api"
    17  	"github.com/juju/go-oracle-cloud/common"
    18  	"github.com/juju/go-oracle-cloud/response"
    19  	"github.com/juju/utils"
    20  
    21  	"github.com/juju/juju/controller"
    22  	corenetwork "github.com/juju/juju/core/network"
    23  	"github.com/juju/juju/environs"
    24  	"github.com/juju/juju/environs/config"
    25  	"github.com/juju/juju/environs/context"
    26  	"github.com/juju/juju/network"
    27  	commonProvider "github.com/juju/juju/provider/oracle/common"
    28  )
    29  
    30  // Firewaller exposes methods for managing network ports.
    31  type Firewaller interface {
    32  	environs.Firewaller
    33  
    34  	// Return all machine ingress rules for a given machine id
    35  	MachineIngressRules(ctx context.ProviderCallContext, id string) ([]network.IngressRule, error)
    36  
    37  	// OpenPortsOnInstance will open ports corresponding to the supplied rules
    38  	// on the given instance
    39  	OpenPortsOnInstance(ctx context.ProviderCallContext, machineId string, rules []network.IngressRule) error
    40  
    41  	// ClosePortsOnInstnace will close ports corresponding to the supplied rules
    42  	// for a given instance.
    43  	ClosePortsOnInstance(ctx context.ProviderCallContext, machineId string, rules []network.IngressRule) error
    44  
    45  	// CreateMachineSecLists creates a security list for the given instance.
    46  	// It's worth noting that this function also ensures that the default environment
    47  	// sec list is also present, and has the appropriate default rules.
    48  	// The port parameter is the API port for the state machine, for which we need
    49  	// to create rules.
    50  	CreateMachineSecLists(id string, port int) ([]string, error)
    51  
    52  	// DeleteMachineSecList will delete the security list on the given machine
    53  	// id
    54  	DeleteMachineSecList(id string) error
    55  
    56  	// CreateDefaultACLAndRules will create a default ACL and associated rules, for
    57  	// a given machine. This ACL applies to user defined IP networks, which are attached
    58  	// to the instance.
    59  	CreateDefaultACLAndRules(id string) (response.Acl, error)
    60  
    61  	// RemoveACLAndRules will remove the ACL and any associated rules.
    62  	RemoveACLAndRules(id string) error
    63  }
    64  
    65  var _ Firewaller = (*Firewall)(nil)
    66  
    67  // FirewallerAPI defines methods necessary for interacting with the firewall
    68  // feature of Oracle compute cloud
    69  type FirewallerAPI interface {
    70  	commonProvider.Composer
    71  	commonProvider.RulesAPI
    72  	commonProvider.AclAPI
    73  	commonProvider.SecIpAPI
    74  	commonProvider.IpAddressPrefixSetAPI
    75  	commonProvider.SecListAPI
    76  	commonProvider.ApplicationsAPI
    77  	commonProvider.SecRulesAPI
    78  	commonProvider.AssociationAPI
    79  }
    80  
    81  // Firewall implements environ.Firewaller
    82  type Firewall struct {
    83  	// environ is the current oracle cloud environment
    84  	// this will use to access the underlying config
    85  	environ environs.ConfigGetter
    86  	// client is used to make operations on the oracle provider
    87  	client FirewallerAPI
    88  	clock  clock.Clock
    89  }
    90  
    91  // NewFirewall returns a new Firewall
    92  func NewFirewall(cfg environs.ConfigGetter, client FirewallerAPI, c clock.Clock) *Firewall {
    93  	return &Firewall{
    94  		environ: cfg,
    95  		client:  client,
    96  		clock:   c,
    97  	}
    98  }
    99  
   100  // OpenPorts is specified on the environ.Firewaller interface.
   101  func (f Firewall) OpenPorts(ctx context.ProviderCallContext, rules []network.IngressRule) error {
   102  	mode := f.environ.Config().FirewallMode()
   103  	if mode != config.FwGlobal {
   104  		return fmt.Errorf(
   105  			"invalid firewall mode %q for opening ports on model",
   106  			mode,
   107  		)
   108  	}
   109  
   110  	globalGroupName := f.globalGroupName()
   111  	seclist, err := f.ensureSecList(f.client.ComposeName(globalGroupName))
   112  	if err != nil {
   113  		return errors.Trace(err)
   114  	}
   115  	err = f.ensureSecRules(seclist, rules)
   116  	if err != nil {
   117  		return errors.Trace(err)
   118  	}
   119  	return nil
   120  }
   121  
   122  // ClosePorts is specified on the environ.Firewaller interface.
   123  func (f Firewall) ClosePorts(ctx context.ProviderCallContext, rules []network.IngressRule) error {
   124  	groupName := f.globalGroupName()
   125  	return f.closePortsOnList(ctx, f.client.ComposeName(groupName), rules)
   126  }
   127  
   128  // IngressRules is specified on the environ.Firewaller interface.
   129  func (f Firewall) IngressRules(ctx context.ProviderCallContext) ([]network.IngressRule, error) {
   130  	return f.GlobalIngressRules(ctx)
   131  }
   132  
   133  // MachineIngressRules returns all ingress rules from the machine specific sec list
   134  func (f Firewall) MachineIngressRules(ctx context.ProviderCallContext, machineId string) ([]network.IngressRule, error) {
   135  	seclist := f.machineGroupName(machineId)
   136  	return f.getIngressRules(ctx, f.client.ComposeName(seclist))
   137  }
   138  
   139  // OpenPortsOnInstance will open ports corresponding to the supplied rules
   140  // on the given instance
   141  func (f Firewall) OpenPortsOnInstance(ctx context.ProviderCallContext, machineId string, rules []network.IngressRule) error {
   142  	machineGroup := f.machineGroupName(machineId)
   143  	seclist, err := f.ensureSecList(f.client.ComposeName(machineGroup))
   144  	if err != nil {
   145  		return errors.Trace(err)
   146  	}
   147  	err = f.ensureSecRules(seclist, rules)
   148  	if err != nil {
   149  		return errors.Trace(err)
   150  	}
   151  	return nil
   152  }
   153  
   154  // ClosePortsOnInstnace will close ports corresponding to the supplied rules
   155  // for a given instance.
   156  func (f Firewall) ClosePortsOnInstance(ctx context.ProviderCallContext, machineId string, rules []network.IngressRule) error {
   157  	// fetch the group name based on the machine id provided
   158  	groupName := f.machineGroupName(machineId)
   159  	return f.closePortsOnList(ctx, f.client.ComposeName(groupName), rules)
   160  }
   161  
   162  // CreateMachineSecLists creates a security list for the given instance.
   163  // It's worth noting that this function also ensures that the default environment
   164  // sec list is also present, and has the appropriate default rules.
   165  // The port parameter is the API port for the state machine, for which we need
   166  // to create rules.
   167  func (f Firewall) CreateMachineSecLists(machineId string, apiPort int) ([]string, error) {
   168  	defaultSecList, err := f.createDefaultGroupAndRules(apiPort)
   169  	if err != nil {
   170  		return nil, errors.Trace(err)
   171  	}
   172  	name := f.machineGroupName(machineId)
   173  	resourceName := f.client.ComposeName(name)
   174  	secList, err := f.ensureSecList(resourceName)
   175  	if err != nil {
   176  		return nil, errors.Trace(err)
   177  	}
   178  	return []string{
   179  		defaultSecList.Name,
   180  		secList.Name,
   181  	}, nil
   182  }
   183  
   184  // DeleteMachineSecList will delete the security list on the given machine
   185  func (f Firewall) DeleteMachineSecList(machineId string) error {
   186  	listName := f.machineGroupName(machineId)
   187  	globalListName := f.globalGroupName()
   188  	err := f.maybeDeleteList(f.client.ComposeName(listName))
   189  	if err != nil {
   190  		return errors.Trace(err)
   191  	}
   192  	// check if we can delete the global list as well
   193  	err = f.maybeDeleteList(f.client.ComposeName(globalListName))
   194  	if err != nil {
   195  		return errors.Trace(err)
   196  	}
   197  	return nil
   198  }
   199  
   200  // CreateDefaultACLAndRules creates default ACL and rules for IP networks attached to
   201  // units.
   202  // NOTE (gsamfira): For now we apply an allow all on these ACLs. Traffic will be cloud-only
   203  // between instances connected to the same ip network exchange (the equivalent of a space)
   204  // There will be no public IP associated to interfaces connected to IP networks, so only
   205  // instances connected to the same network, or a network managed by the same space will
   206  // be able to connect. This will ensure that peers and units entering a relationship can connect
   207  // to services deployed by a particular unit, without having to expose the application.
   208  func (f Firewall) CreateDefaultACLAndRules(machineId string) (response.Acl, error) {
   209  	var details response.Acl
   210  	var err error
   211  	description := fmt.Sprintf("ACL for machine %s", machineId)
   212  	groupName := f.machineGroupName(machineId)
   213  	resourceName := f.client.ComposeName(groupName)
   214  	if err != nil {
   215  		return response.Acl{}, err
   216  	}
   217  	rules := []api.SecurityRuleParams{
   218  		{
   219  			Name:                   fmt.Sprintf("%s-allow-ingress", resourceName),
   220  			Description:            "Allow all ingress",
   221  			FlowDirection:          common.Ingress,
   222  			EnabledFlag:            true,
   223  			DstIpAddressPrefixSets: []string{},
   224  			SecProtocols:           []string{},
   225  			SrcIpAddressPrefixSets: []string{},
   226  		},
   227  		{
   228  			Name:                   fmt.Sprintf("%s-allow-egress", resourceName),
   229  			Description:            "Allow all egress",
   230  			FlowDirection:          common.Egress,
   231  			EnabledFlag:            true,
   232  			DstIpAddressPrefixSets: []string{},
   233  			SecProtocols:           []string{},
   234  			SrcIpAddressPrefixSets: []string{},
   235  		},
   236  	}
   237  	details, err = f.client.AclDetails(resourceName)
   238  	if err != nil {
   239  		if api.IsNotFound(err) {
   240  			details, err = f.client.CreateAcl(resourceName, description, true, nil)
   241  			if err != nil {
   242  				return response.Acl{}, errors.Trace(err)
   243  			}
   244  		} else {
   245  			return response.Acl{}, errors.Trace(err)
   246  		}
   247  	}
   248  	aclRules, err := f.getAllSecurityRules(details.Name)
   249  	if err != nil {
   250  		return response.Acl{}, errors.Trace(err)
   251  	}
   252  
   253  	var toAdd []api.SecurityRuleParams
   254  
   255  	for _, val := range rules {
   256  		found := false
   257  		val.Acl = details.Name
   258  		newRuleAsStub := f.convertSecurityRuleParamsToStub(val)
   259  		for _, existing := range aclRules {
   260  			existingRulesAsStub := f.convertSecurityRuleToStub(existing)
   261  			if reflect.DeepEqual(existingRulesAsStub, newRuleAsStub) {
   262  				found = true
   263  				break
   264  			}
   265  		}
   266  		if !found {
   267  			toAdd = append(toAdd, val)
   268  		}
   269  	}
   270  	for _, val := range toAdd {
   271  		_, err := f.client.CreateSecurityRule(val)
   272  		if err != nil {
   273  			return response.Acl{}, errors.Trace(err)
   274  		}
   275  	}
   276  	return details, nil
   277  }
   278  
   279  // RemoveACLAndRules will remove the ACL and any associated rules.
   280  func (f Firewall) RemoveACLAndRules(machineId string) error {
   281  	groupName := f.machineGroupName(machineId)
   282  	resourceName := f.client.ComposeName(groupName)
   283  	secRules, err := f.getAllSecurityRules(resourceName)
   284  	if err != nil {
   285  		return err
   286  	}
   287  	for _, val := range secRules {
   288  		err := f.client.DeleteSecurityRule(val.Name)
   289  		if err != nil {
   290  			if !api.IsNotFound(err) {
   291  				return err
   292  			}
   293  		}
   294  	}
   295  	err = f.client.DeleteAcl(resourceName)
   296  	if err != nil {
   297  		if !api.IsNotFound(err) {
   298  			return err
   299  		}
   300  	}
   301  	return nil
   302  }
   303  
   304  // GlobalIngressRules returns the ingress rules applied to the whole environment.
   305  func (f Firewall) GlobalIngressRules(ctx context.ProviderCallContext) ([]network.IngressRule, error) {
   306  	seclist := f.globalGroupName()
   307  	return f.getIngressRules(ctx, f.client.ComposeName(seclist))
   308  }
   309  
   310  // getDefaultIngressRules will create the default ingressRules given an api port
   311  func (f Firewall) getDefaultIngressRules(apiPort int) []network.IngressRule {
   312  	return []network.IngressRule{
   313  		{
   314  			PortRange: corenetwork.PortRange{
   315  				FromPort: 22,
   316  				ToPort:   22,
   317  				Protocol: "tcp",
   318  			},
   319  			SourceCIDRs: []string{
   320  				"0.0.0.0/0",
   321  			},
   322  		},
   323  		{
   324  			PortRange: corenetwork.PortRange{
   325  				FromPort: 3389,
   326  				ToPort:   3389,
   327  				Protocol: "tcp",
   328  			},
   329  			SourceCIDRs: []string{
   330  				"0.0.0.0/0",
   331  			},
   332  		},
   333  		{
   334  			PortRange: corenetwork.PortRange{
   335  				FromPort: apiPort,
   336  				ToPort:   apiPort,
   337  				Protocol: "tcp",
   338  			},
   339  			SourceCIDRs: []string{
   340  				"0.0.0.0/0",
   341  			},
   342  		},
   343  		{
   344  			PortRange: corenetwork.PortRange{
   345  				FromPort: controller.DefaultStatePort,
   346  				ToPort:   controller.DefaultStatePort,
   347  				Protocol: "tcp",
   348  			},
   349  			SourceCIDRs: []string{
   350  				"0.0.0.0/0",
   351  			},
   352  		},
   353  	}
   354  }
   355  
   356  type stubSecurityRule struct {
   357  	Acl                    string
   358  	FlowDirection          common.FlowDirection
   359  	DstIpAddressPrefixSets []string
   360  	SecProtocols           []string
   361  	SrcIpAddressPrefixSets []string
   362  }
   363  
   364  func (f Firewall) createDefaultGroupAndRules(apiPort int) (response.SecList, error) {
   365  	rules := f.getDefaultIngressRules(apiPort)
   366  	var details response.SecList
   367  	var err error
   368  	globalGroupName := f.globalGroupName()
   369  	resourceName := f.client.ComposeName(globalGroupName)
   370  	details, err = f.client.SecListDetails(resourceName)
   371  	if err != nil {
   372  		if api.IsNotFound(err) {
   373  			details, err = f.ensureSecList(resourceName)
   374  			if err != nil {
   375  				return response.SecList{}, errors.Trace(err)
   376  			}
   377  		} else {
   378  			return response.SecList{}, errors.Trace(err)
   379  		}
   380  	}
   381  
   382  	err = f.ensureSecRules(details, rules)
   383  	if err != nil {
   384  		return response.SecList{}, errors.Trace(err)
   385  	}
   386  	return details, nil
   387  }
   388  
   389  // closePortsOnList on list will close all ports corresponding to the supplied ingress rules
   390  // on a particular list
   391  func (f Firewall) closePortsOnList(ctx context.ProviderCallContext, list string, rules []network.IngressRule) error {
   392  	// get all security rules based on the dst_list=list
   393  	secrules, err := f.getSecRules(list)
   394  	if err != nil {
   395  		return errors.Trace(err)
   396  	}
   397  	// converts all security rules into a map of ingress rules
   398  	mapping, err := f.secRuleToIngresRule(secrules...)
   399  	if err != nil {
   400  		return errors.Trace(err)
   401  	}
   402  
   403  	//TODO (gsamfira): optimize this
   404  	for name, rule := range mapping {
   405  		sort.Strings(rule.SourceCIDRs)
   406  		for _, ingressRule := range rules {
   407  			sort.Strings(ingressRule.SourceCIDRs)
   408  			if reflect.DeepEqual(rule, ingressRule) {
   409  				err := f.client.DeleteSecRule(name)
   410  				if err != nil {
   411  					return errors.Trace(err)
   412  				}
   413  			}
   414  		}
   415  	}
   416  	return nil
   417  }
   418  
   419  // deleteAllSecRulesOnList will delete all security rules from a give
   420  // security list
   421  func (f Firewall) deleteAllSecRulesOnList(list string) error {
   422  	// get all security rules associated with this list
   423  	secrules, err := f.getSecRules(list)
   424  	if err != nil {
   425  		return errors.Trace(err)
   426  	}
   427  	// delete everything
   428  	for _, rule := range secrules {
   429  		err := f.client.DeleteSecRule(rule.Name)
   430  		if err != nil {
   431  			if api.IsNotFound(err) {
   432  				continue
   433  			}
   434  			return errors.Trace(err)
   435  		}
   436  	}
   437  	return nil
   438  }
   439  
   440  // maybeDeleteList tries to delete a security list. Lists that are still in use
   441  // may not be deleted. When deleting an environment, we want to also cleanup the
   442  // environment level sec list. This function attempts to delete a sec list. If the
   443  // sec list still has some associations to any instance, we simply return and assume
   444  // the last VM to get killed as part of the tear-down, will also remove the global
   445  // list as well
   446  func (f *Firewall) maybeDeleteList(list string) error {
   447  	filter := []api.Filter{
   448  		{
   449  			Arg:   "seclist",
   450  			Value: list,
   451  		},
   452  	}
   453  	iter := 0
   454  	found := true
   455  	var assoc response.AllSecAssociations
   456  	for {
   457  		if iter >= 10 {
   458  			break
   459  		}
   460  		assoc, err := f.client.AllSecAssociations(filter)
   461  		if err != nil {
   462  			return errors.Trace(err)
   463  		}
   464  		if len(assoc.Result) > 0 {
   465  			<-f.clock.After(1 * time.Second)
   466  			iter++
   467  			continue
   468  		}
   469  		found = false
   470  		break
   471  	}
   472  	if found {
   473  		logger.Warningf(
   474  			"seclist %s is still has some associations to instance(s): %v. Will not delete",
   475  			list, assoc.Result,
   476  		)
   477  		return nil
   478  	}
   479  	err := f.deleteAllSecRulesOnList(list)
   480  	if err != nil {
   481  		return errors.Trace(err)
   482  	}
   483  	logger.Tracef("deleting seclist %v", list)
   484  	err = f.client.DeleteSecList(list)
   485  	if err != nil {
   486  		if api.IsNotFound(err) {
   487  			return nil
   488  		}
   489  		return errors.Trace(err)
   490  	}
   491  	return nil
   492  }
   493  
   494  // getIngressRules returns all rules associated with the given sec list
   495  // values are converted and returned as []network.IngressRule
   496  func (f Firewall) getIngressRules(ctx context.ProviderCallContext, seclist string) ([]network.IngressRule, error) {
   497  	// get all security rules associated with the seclist
   498  	secrules, err := f.getSecRules(seclist)
   499  	if err != nil {
   500  		return nil, errors.Trace(err)
   501  	}
   502  	// convert all security rules into a map of ingress rules
   503  	ingressRules, err := f.convertFromSecRules(secrules...)
   504  	if err != nil {
   505  		return nil, errors.Trace(err)
   506  	}
   507  	if rules, ok := ingressRules[seclist]; ok {
   508  		return rules, nil
   509  	}
   510  	return []network.IngressRule{}, nil
   511  }
   512  
   513  // getAllApplications returns all security applications known to the
   514  // oracle compute cloud. These are used as part of security rules
   515  func (f Firewall) getAllApplications() ([]response.SecApplication, error) {
   516  	// get all user defined sec applications
   517  	applications, err := f.client.AllSecApplications(nil)
   518  	if err != nil {
   519  		return nil, errors.Trace(err)
   520  	}
   521  	// get also default ones defined in the provider
   522  	defaultApps, err := f.client.DefaultSecApplications(nil)
   523  	if err != nil {
   524  		return nil, errors.Trace(err)
   525  	}
   526  	allApps := []response.SecApplication{}
   527  	for _, val := range applications.Result {
   528  		if val.PortProtocolPair() == "" {
   529  			// (gsamfira):this should not really happen,
   530  			// but I get paranoid when I run out of coffee
   531  			continue
   532  		}
   533  		allApps = append(allApps, val)
   534  	}
   535  	for _, val := range defaultApps.Result {
   536  		if val.PortProtocolPair() == "" {
   537  			continue
   538  		}
   539  		allApps = append(allApps, val)
   540  	}
   541  	return allApps, nil
   542  }
   543  
   544  // getAllApplicationsAsMap returns all sec applications as a map
   545  func (f Firewall) getAllApplicationsAsMap() (map[string]response.SecApplication, error) {
   546  	// get all defined protocols
   547  	// from the current identity and default ones
   548  	apps, err := f.getAllApplications()
   549  	if err != nil {
   550  		return nil, errors.Trace(err)
   551  	}
   552  	// copy all of them into this map
   553  	allApps := map[string]response.SecApplication{}
   554  	for _, val := range apps {
   555  		if val.String() == "" {
   556  			continue
   557  		}
   558  		if _, ok := allApps[val.String()]; !ok {
   559  			allApps[val.String()] = val
   560  		}
   561  	}
   562  	return allApps, nil
   563  }
   564  
   565  func (f Firewall) ensureApplication(portRange corenetwork.PortRange, cache *[]response.SecApplication) (string, error) {
   566  	// check if the security application is already created
   567  	for _, val := range *cache {
   568  		if val.PortProtocolPair() == portRange.String() {
   569  			return val.Name, nil
   570  		}
   571  	}
   572  	// We need to create a new application
   573  	// There is always the chance of a race condition
   574  	// when it comes to creating new resources.
   575  	// ie: someone may have already created a matching
   576  	// application between the time we fetched all of them
   577  	// and the moment we actually got to create one
   578  	// Worst thing that can happen is that we have a few duplicate
   579  	// rules, that we cleanup anyway when we destroy the environment
   580  	uuid, err := utils.NewUUID()
   581  	if err != nil {
   582  		return "", errors.Trace(err)
   583  	}
   584  	// create new name for sec application
   585  	secAppName := f.newResourceName(uuid.String())
   586  	var dport string
   587  
   588  	if portRange.FromPort == portRange.ToPort {
   589  		dport = strconv.Itoa(portRange.FromPort)
   590  	} else {
   591  		dport = fmt.Sprintf("%s-%s",
   592  			strconv.Itoa(portRange.FromPort), strconv.Itoa(portRange.ToPort))
   593  	}
   594  	// compose the provider resource name for the new application
   595  	name := f.client.ComposeName(secAppName)
   596  	secAppParams := api.SecApplicationParams{
   597  		Description: "Juju created security application",
   598  		Dport:       dport,
   599  		Protocol:    common.Protocol(portRange.Protocol),
   600  		Name:        name,
   601  	}
   602  	application, err := f.client.CreateSecApplication(secAppParams)
   603  	if err != nil {
   604  		return "", errors.Trace(err)
   605  	}
   606  	*cache = append(*cache, application)
   607  	return application.Name, nil
   608  }
   609  
   610  // convertToSecRules converts network.IngressRules to api.SecRuleParams
   611  func (f Firewall) convertToSecRules(seclist response.SecList, rules []network.IngressRule) ([]api.SecRuleParams, error) {
   612  	applications, err := f.getAllApplications()
   613  	if err != nil {
   614  		return nil, errors.Trace(err)
   615  	}
   616  	iplists, err := f.getAllIPLists()
   617  	if err != nil {
   618  		return nil, errors.Trace(err)
   619  	}
   620  
   621  	ret := make([]api.SecRuleParams, 0, len(rules))
   622  	// for every rule we need to ensure that the there is a relationship
   623  	// between security applications and security IP lists
   624  	// and from every one of them create a slice of security rule parameters
   625  	for _, val := range rules {
   626  		app, err := f.ensureApplication(val.PortRange, &applications)
   627  		if err != nil {
   628  			return nil, errors.Trace(err)
   629  		}
   630  		ipList, err := f.ensureSecIpList(val.SourceCIDRs, &iplists)
   631  		if err != nil {
   632  			return nil, errors.Trace(err)
   633  		}
   634  		uuid, err := utils.NewUUID()
   635  		if err != nil {
   636  			return nil, errors.Trace(err)
   637  		}
   638  		name := f.newResourceName(uuid.String())
   639  		resourceName := f.client.ComposeName(name)
   640  		dstList := fmt.Sprintf("seclist:%s", seclist.Name)
   641  		srcList := fmt.Sprintf("seciplist:%s", ipList)
   642  		// create the new security rule parameters
   643  		rule := api.SecRuleParams{
   644  			Action:      common.SecRulePermit,
   645  			Application: app,
   646  			Description: "Juju created security rule",
   647  			Disabled:    false,
   648  			Dst_list:    dstList,
   649  			Name:        resourceName,
   650  			Src_list:    srcList,
   651  		}
   652  		// append the new parameters rule
   653  		ret = append(ret, rule)
   654  	}
   655  	return ret, nil
   656  }
   657  
   658  // convertApplicationToPortRange takes a SecApplication and
   659  // converts it to a network.PortRange type
   660  func (f Firewall) convertApplicationToPortRange(app response.SecApplication) corenetwork.PortRange {
   661  	appCopy := app
   662  	if appCopy.Value2 == -1 {
   663  		appCopy.Value2 = appCopy.Value1
   664  	}
   665  	return corenetwork.PortRange{
   666  		FromPort: appCopy.Value1,
   667  		ToPort:   appCopy.Value2,
   668  		Protocol: string(appCopy.Protocol),
   669  	}
   670  }
   671  
   672  // convertFromSecRules takes a slice of security rules and creates a map of them
   673  func (f Firewall) convertFromSecRules(rules ...response.SecRule) (map[string][]network.IngressRule, error) {
   674  	applications, err := f.getAllApplicationsAsMap()
   675  	if err != nil {
   676  		return nil, errors.Trace(err)
   677  	}
   678  
   679  	iplists, err := f.getAllIPListsAsMap()
   680  	if err != nil {
   681  		return nil, errors.Trace(err)
   682  	}
   683  
   684  	ret := map[string][]network.IngressRule{}
   685  	for _, val := range rules {
   686  		app := val.Application
   687  		srcList := strings.TrimPrefix(val.Src_list, "seciplist:")
   688  		dstList := strings.TrimPrefix(val.Dst_list, "seclist:")
   689  		portRange := f.convertApplicationToPortRange(applications[app])
   690  		if _, ok := ret[dstList]; !ok {
   691  			ret[dstList] = []network.IngressRule{
   692  				{
   693  					PortRange:   portRange,
   694  					SourceCIDRs: iplists[srcList].Secipentries,
   695  				},
   696  			}
   697  		} else {
   698  			toAdd := network.IngressRule{
   699  				PortRange:   portRange,
   700  				SourceCIDRs: iplists[srcList].Secipentries,
   701  			}
   702  			ret[dstList] = append(ret[dstList], toAdd)
   703  		}
   704  	}
   705  	return ret, nil
   706  }
   707  
   708  // secRuleToIngressRule convert all security rules into a map of ingress rules
   709  func (f Firewall) secRuleToIngresRule(rules ...response.SecRule) (map[string]network.IngressRule, error) {
   710  
   711  	applications, err := f.getAllApplicationsAsMap()
   712  	if err != nil {
   713  		return nil, errors.Trace(err)
   714  	}
   715  	iplists, err := f.getAllIPListsAsMap()
   716  	if err != nil {
   717  		return nil, errors.Trace(err)
   718  	}
   719  
   720  	ret := map[string]network.IngressRule{}
   721  	for _, val := range rules {
   722  		app := val.Application
   723  		srcList := strings.TrimPrefix(val.Src_list, "seciplist:")
   724  		portRange := f.convertApplicationToPortRange(applications[app])
   725  		if _, ok := ret[val.Name]; !ok {
   726  			ret[val.Name] = network.IngressRule{
   727  				PortRange:   portRange,
   728  				SourceCIDRs: iplists[srcList].Secipentries,
   729  			}
   730  		}
   731  	}
   732  	return ret, nil
   733  }
   734  
   735  func (f Firewall) convertSecurityRuleToStub(rules response.SecurityRule) stubSecurityRule {
   736  	sort.Strings(rules.DstIpAddressPrefixSets)
   737  	sort.Strings(rules.SecProtocols)
   738  	sort.Strings(rules.SrcIpAddressPrefixSets)
   739  	return stubSecurityRule{
   740  		Acl:                    rules.Acl,
   741  		FlowDirection:          rules.FlowDirection,
   742  		DstIpAddressPrefixSets: rules.DstIpAddressPrefixSets,
   743  		SecProtocols:           rules.SecProtocols,
   744  		SrcIpAddressPrefixSets: rules.SrcIpAddressPrefixSets,
   745  	}
   746  }
   747  
   748  func (f Firewall) convertSecurityRuleParamsToStub(params api.SecurityRuleParams) stubSecurityRule {
   749  	sort.Strings(params.DstIpAddressPrefixSets)
   750  	sort.Strings(params.SecProtocols)
   751  	sort.Strings(params.SrcIpAddressPrefixSets)
   752  	return stubSecurityRule{
   753  		Acl:                    params.Acl,
   754  		FlowDirection:          params.FlowDirection,
   755  		DstIpAddressPrefixSets: params.DstIpAddressPrefixSets,
   756  		SecProtocols:           params.SecProtocols,
   757  		SrcIpAddressPrefixSets: params.SrcIpAddressPrefixSets,
   758  	}
   759  }
   760  
   761  // globalGroupName returns the global group name
   762  // derived from the model UUID
   763  func (f Firewall) globalGroupName() string {
   764  	return fmt.Sprintf("juju-%s-global", f.environ.Config().UUID())
   765  }
   766  
   767  // machineGroupName returns the machine group name
   768  // derived from the model UUID and the machine ID
   769  func (f Firewall) machineGroupName(machineId string) string {
   770  	return fmt.Sprintf("juju-%s-%s", f.environ.Config().UUID(), machineId)
   771  }
   772  
   773  // resourceName returns the resource name
   774  // derived from the model UUID and the name of the resource
   775  func (f Firewall) newResourceName(appName string) string {
   776  	return fmt.Sprintf("juju-%s-%s", f.environ.Config().UUID(), appName)
   777  }
   778  
   779  // get all security rules associated with an ACL
   780  func (f Firewall) getAllSecurityRules(aclName string) ([]response.SecurityRule, error) {
   781  	rules, err := f.client.AllSecurityRules(nil)
   782  	if err != nil {
   783  		return nil, err
   784  	}
   785  	if aclName == "" {
   786  		return rules.Result, nil
   787  	}
   788  	var ret []response.SecurityRule
   789  	for _, val := range rules.Result {
   790  		if val.Acl == aclName {
   791  			ret = append(ret, val)
   792  		}
   793  	}
   794  	return ret, nil
   795  }
   796  
   797  // getSecRules retrieves the security rules associated with a particular security list
   798  func (f Firewall) getSecRules(seclist string) ([]response.SecRule, error) {
   799  	// we only care about ingress rules
   800  	name := fmt.Sprintf("seclist:%s", seclist)
   801  	rulesFilter := []api.Filter{
   802  		{
   803  			Arg:   "dst_list",
   804  			Value: name,
   805  		},
   806  	}
   807  	rules, err := f.client.AllSecRules(rulesFilter)
   808  	if err != nil {
   809  		return nil, errors.Trace(err)
   810  	}
   811  	// gsamfira: the oracle compute API does not allow filtering by action
   812  	ret := []response.SecRule{}
   813  	for _, val := range rules.Result {
   814  		// gsamfira: We set a default policy of DENY. No use in worrying about
   815  		// DENY rules (if by any chance someone add one manually for some reason)
   816  		if val.Action != common.SecRulePermit {
   817  			continue
   818  		}
   819  		// We only care about rules that have a destination set
   820  		// to a security list. Those lists get attached to VMs
   821  		// NOTE: someone decided, when writing the oracle API
   822  		// that some fields should be bool, some should be string.
   823  		// never mind they both are boolean values...but hey.
   824  		// I swear...some people like to watch the world burn
   825  		if val.Dst_is_ip == "true" {
   826  			continue
   827  		}
   828  		// We only care about rules that have an IP list as source
   829  		if val.Src_is_ip == "false" {
   830  			continue
   831  		}
   832  		ret = append(ret, val)
   833  	}
   834  	return ret, nil
   835  }
   836  
   837  // ensureSecRules ensures that the list passed has all the rules
   838  // that it needs, if one is missing it will create it inside the oracle
   839  // cloud environment and it will return nil
   840  // if none rule is missing then it will return nil
   841  func (f Firewall) ensureSecRules(seclist response.SecList, rules []network.IngressRule) error {
   842  	// get all security rules associated with the seclist
   843  	secRules, err := f.getSecRules(seclist.Name)
   844  	if err != nil {
   845  		return errors.Trace(err)
   846  	}
   847  	logger.Tracef("list %v has sec rules: %v", seclist.Name, secRules)
   848  
   849  	converted, err := f.convertFromSecRules(secRules...)
   850  	if err != nil {
   851  		return errors.Trace(err)
   852  	}
   853  	logger.Tracef("converted rules are: %v", converted)
   854  	asIngressRules := converted[seclist.Name]
   855  	missing := []network.IngressRule{}
   856  
   857  	// search through all rules and find the missing ones
   858  	for _, toAdd := range rules {
   859  		found := false
   860  		for _, exists := range asIngressRules {
   861  			sort.Strings(toAdd.SourceCIDRs)
   862  			sort.Strings(exists.SourceCIDRs)
   863  			logger.Tracef("comparing %v to %v", toAdd.SourceCIDRs, exists.SourceCIDRs)
   864  			if reflect.DeepEqual(toAdd, exists) {
   865  				found = true
   866  				break
   867  			}
   868  		}
   869  		if found {
   870  			continue
   871  		}
   872  		missing = append(missing, toAdd)
   873  	}
   874  	if len(missing) == 0 {
   875  		return nil
   876  	}
   877  	logger.Tracef("Found missing rules: %v", missing)
   878  	// convert the missing rules back to sec rules
   879  	asSecRule, err := f.convertToSecRules(seclist, missing)
   880  	if err != nil {
   881  		return errors.Trace(err)
   882  	}
   883  
   884  	for _, val := range asSecRule {
   885  		_, err = f.client.CreateSecRule(val)
   886  		if err != nil {
   887  			return errors.Trace(err)
   888  		}
   889  	}
   890  	return nil
   891  }
   892  
   893  // ensureSecList creates a new seclist if one does not already exist.
   894  // this function is idempotent
   895  func (f Firewall) ensureSecList(name string) (response.SecList, error) {
   896  	logger.Infof("Fetching details for list: %s", name)
   897  	// check if the security list is already there
   898  	details, err := f.client.SecListDetails(name)
   899  	if err != nil {
   900  		logger.Infof("Got error fetching details for %s: %v", name, err)
   901  		if api.IsNotFound(err) {
   902  			logger.Infof("Creating new seclist: %s", name)
   903  			details, err := f.client.CreateSecList(
   904  				"Juju created security list",
   905  				name,
   906  				common.SecRulePermit,
   907  				common.SecRuleDeny)
   908  			if err != nil {
   909  				return response.SecList{}, err
   910  			}
   911  			return details, nil
   912  		}
   913  		return response.SecList{}, err
   914  	}
   915  	return details, nil
   916  }
   917  
   918  // getAllIPLists returns all IP lists known to the provider (both the user defined ones,
   919  // and the default ones)
   920  func (f Firewall) getAllIPLists() ([]response.SecIpList, error) {
   921  	// get all security ip lists from the current identity endpoint
   922  	secIpLists, err := f.client.AllSecIpLists(nil)
   923  	if err != nil {
   924  		return nil, errors.Trace(err)
   925  	}
   926  	// get all security ip lists from the default oracle cloud definitions
   927  	defaultSecIpLists, err := f.client.AllDefaultSecIpLists(nil)
   928  	if err != nil {
   929  		return nil, errors.Trace(err)
   930  	}
   931  
   932  	allIpLists := []response.SecIpList{}
   933  	allIpLists = append(allIpLists, secIpLists.Result...)
   934  	allIpLists = append(allIpLists, defaultSecIpLists.Result...)
   935  	return allIpLists, nil
   936  }
   937  
   938  // getAllIPListsAsMap returns all IP lists as a map, with the key being
   939  // the resource name
   940  func (f Firewall) getAllIPListsAsMap() (map[string]response.SecIpList, error) {
   941  	allIps, err := f.getAllIPLists()
   942  	if err != nil {
   943  		return nil, errors.Trace(err)
   944  	}
   945  	allIpLists := map[string]response.SecIpList{}
   946  	for _, val := range allIps {
   947  		allIpLists[val.Name] = val
   948  	}
   949  	return allIpLists, nil
   950  }
   951  
   952  // ensureSecIpList ensures that a sec ip list with the provided cidr list
   953  // exists. If one does not, it gets created. This function is idempotent.
   954  func (f Firewall) ensureSecIpList(cidr []string, cache *[]response.SecIpList) (string, error) {
   955  	sort.Strings(cidr)
   956  	for _, val := range *cache {
   957  		sort.Strings(val.Secipentries)
   958  		if reflect.DeepEqual(val.Secipentries, cidr) {
   959  			return val.Name, nil
   960  		}
   961  	}
   962  	uuid, err := utils.NewUUID()
   963  	if err != nil {
   964  		return "", errors.Trace(err)
   965  	}
   966  	name := f.newResourceName(uuid.String())
   967  	resource := f.client.ComposeName(name)
   968  	secList, err := f.client.CreateSecIpList(
   969  		"Juju created security IP list",
   970  		resource, cidr)
   971  	if err != nil {
   972  		return "", errors.Trace(err)
   973  	}
   974  	*cache = append(*cache, secList)
   975  	return secList.Name, nil
   976  }