Coder Social home page Coder Social logo

paloaltonetworks / terraform-panos-ngfw-modules Goto Github PK

View Code? Open in Web Editor NEW
5.0 10.0 1.0 368 KB

Terraform Modules for Palo Alto Networks PAN-OS based Platforms

Home Page: https://registry.terraform.io/modules/PaloAltoNetworks/ngfw-modules/panos

License: MIT License

HCL 92.90% Go 6.68% Makefile 0.41%
configuration-management pan-os terraform

terraform-panos-ngfw-modules's Introduction

GitHub release (latest by date) GitHub GitHub Workflow Status GitHub issues GitHub pull requests Terraform registry downloads total Terraform registry download month

Terraform Modules for Palo Alto Networks PAN-OS Based Platforms

Overview

A set of Terraform modules that can be leveraged to configure and manage Palo Alto Networks PAN-OS-based platforms (Firewalls and Panorama) using code.

This solution enables you to manage various configuration aspects, including Tags, Address Objects/Groups, Security/NAT policies, Security Profiles, and more.

Structure

This repository has the following directory structure:

  • modules: This directory contains several standalone, reusable, production-grade Terraform modules. Each module is individually documented.
  • examples: This directory shows examples of different ways to combine the modules contained in the modules directory.

Compatibility

These modules are meant for use with PAN-OS >= 10.x.x and Terraform >= 1.4

Setup

Important

Modes

The modules are designed to seamlessly integrate with either a PAN-OS firewall or a Panorama instance, providing flexibility in their usage. The user is required to implicitly pass the mode variable to these modules, which dictates the operational context of the modules. This variable is mandatory, with accepted values being panorama or ngfw.

The underlying panos provider can be configured using the following methods.

For all the supported arguments, please refer to provider documentation

  1. Directly in the provider block
provider "panos" {
  hostname = "1.1.1.1"
  username = "username"
  password= "password" 
}
  1. Environment variable setting (where applicable)
export PANOS_HOSTNAME=
export PANOS_USERNAME=
export PANOS_API_KEY=
  1. From a JSON config file
> cat ./panos-config.json
{
  "hostname": "1.1.1.1",
  "username": "user",
  "password": "password"
}
provider "panos" {
  json_config_file = "panos-config.json"
}

Testing

To execute tests, create the folder tests/creds/ with below two files:

  • panorama.json
  • firewall.json

which will contain credentials to access Panorama and firewall instances, e.g.:

{
  "hostname": "1.1.1.1",
  "username": "user",
  "password": "password"
}

When credentials files are ready, use the below commands to run tests:

cd tests
go mod init github.com/PaloAltoNetworks/terraform-panos-modules/tests
go mod tidy
go test -v -timeout 30m -count=1

Versioning

These modules follow the principles of Semantic Versioning. You can find each new release, along with the changelog on the GitHub Releases page.

Getting Help

If you have found a bug, please report it. The preferred way is to create a new issue on the GitHub issue page.

terraform-panos-ngfw-modules's People

Contributors

jabielecki avatar michalbil avatar migara avatar pimielowski avatar sebastianczech avatar sokupski avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

terraform-panos-ngfw-modules's Issues

Module device_group module template

Is your feature request related to a problem?

No response

Describe the solution you'd like

No response

Describe alternatives you've considered.

No response

Additional context

No response

fix(modules/network): the local IP address (local ID value) is not configured by resource panos_ike_gateway (value is passed to the resource)

Describe the bug

On lab environment, while configuring device using basic example, there are problems with getting IP address for ethernet 1/1, which is configured as local interface for IKE gateway. The local IP address (local ID value) is passed to the resource panos_ike_gateway, but on VM-Series it's empty

Expected behavior

Ethernet 1/1 and local IP address are configured for IKE gateway.

Current behavior

Ethernet 1/1 is configured for IKE gateway, but local IP address is empty.

Order of policy and NAT rules is not correct, program for pushing changes from Panorama to VM series needs improvement

Describe the bug

There are 2 problems:

  • order of policy and NAT rules is not correct
  • program for pushing changes from Panorama to VM series needs improvement and exit with proper code when commit or push is failing

Expected behavior

  • order of policy and NAT rules is correct
  • program for pushing changes from Panorama to VM series is failing when commit or push ends with failure

Context

Both problems were observed during preparing demo for policy as a code

Pipeline for pushing configuration from Panorama to devices is failing

Describe the bug

In Gitlab pipeline while pushing configuration from Panorama to devices there is failing.

Current behavior

2022/09/26 12:26:56.464775 Devices [AWS GlobalProtect Firewalls]
2022/09/26 12:26:56.580514 Error in commit: commit-all -> shared-policy -> device-group ->  '' needs to be at least 1 character | commit-all -> shared-policy -> device-group is invalid

Expected behavior

Using pipeline configuration can be pushed from to devices.

[Bug Report] Packet Buffer Protection not enabled for zones

Describe the bug

While configuring zones using module zone resource panos_zone is not configuring Packet Buffer Protection, which by default is enabled for every zone created from UI in PANOS. Moreover that option is not present in panos provider: https://registry.terraform.io/providers/PaloAltoNetworks/panos/latest/docs/resources/zone.

Module Version

No release at this moment, commit 96fd53d

Terraform version

1.4.5

Expected behavior

There is possibility to enable/disable Packet Buffer Protection or it's enabled by default (as in web UI).

Current behavior

Zones external, internal, mgmt were created implicitly while configuring interfaces and option Packet Buffer Protection is enabled.
Zones Trust-L3 and Untrust-L3 were configured directly by resource panos_zone and option Packet Buffer Protection is disabled.

Anything else to add?

Screenshot 2023-04-25 at 09 05 00

Refactor Examples

  • Remove nested directories and move all examples under examples/ folder

feat(pango tools): upgrade commit and push behaviour

Is your feature request related to a problem?

Right now we commit and push every changed with our commit & push tools, we need to provide safety to this process.

  • Check if already there is pending changes and if selected user has something not committed
  • Check if there are not synced changed of device groups and template stacks
  • Selective commit - commit everything for a selected user
  • Selective pushing - push everything or push to specific devices

Module template and template_stack

Is your feature request related to a problem?

No response

Describe the solution you'd like

No response

Describe alternatives you've considered.

No response

Additional context

No response

Problem with configuring VM-series by pushing configuration defined in Panorama

Describe the bug

While configuring Panorama using CSV basic example, there are problems with:

  • interfaces in static routes
  • IKE gateway configuration
  • default virtual system for VM series

Current behavior

While committing and pushing configuration from Panorama to VM-series, process is failing

Context

Problems were detected while preparing for the demo

Move "mode_map" and "tag_color_map" (tags module) input variables to a local ones

Right now two input mode-related variables are exposed for users: mode and mode_map. The latter should rather be treated as an internal one, as in general there is no reason for users to modify it's value.

As an enhancement, the mode_map input variable can be removed in favor of a local variable defined within modules that will serve the same purpose.

Same applies for the tag_color_map variable in tags module.

Redefine the data models for all the models

Addresses

variables

  • device_group
  • addresses
  • address_groups
 {
    "device_group" = "my_device_group"
    "address_groups" = {
      "dns" = {
        "members" = [
          "google_dns",
          "cloudflare",
        ]
      }
    }
    "addresses" = {
      "cloudflare" = {
        "type" = "ip_netmask"
        "value" = "1.1.1.1"
      }
      "google_dns" = {
        "type" = "ip_netmask"
        "value" = "8.8.8.8"
      }
    }
  }

Services

variables

  • device_group
  • services
  • service_groups
  {
    "device_group" = "my_device_group"
    "service_groups" = {
      "my_services" = {
        "members" = [
          "TCP-8000",
          "TCP-8443",
        ]
      }
    }
    "services" = {
      "TCP-8000" = {
        "destination_port" = "8000"
        "protocol" = "tcp"
      }
      "TCP-8443" = {
        "destination_port" = "8443"
        "protocol" = "tcp"
      }
    }
  }

Tags

variables

  • device_group
  • tags
{
  "device_group" = "my_device_group"
  "tags": {
    "tag_foo": {
      "color": "Blue",
      "comment": "hello world",
      "vsys": "vsys1"
    },
    "tag_bar": {
      "color": "Red"
    }
  }
}

Security Policies

  • device_group
  • security_policies
{
  "allow_rule_group" = {
    rulebase = "pre-rulebase"
    policies_rules = [
      {
        name = "Allow access to DNS Servers"
        tags     = [
          "Outbound",
          "Managed by Terraform"
        ]
        source_zones                       = ["Trust-L3"]
        source_addresses                   = ["RFC1918_Subnets"]
        negate_source                      = "false"
        source_users                       = ["any"]
        hip_profiles                       = ["any"]
        destination_zones                  = ["Untrust-L3"]
        destination_addresses              = ["DNS-Servers"]
        negate_destination                 = "false"
        applications                       = ["dns"]
        services                           = ["application-default"]
        categories                         = ["any"]
        action                             = "allow"
        disable_server_response_inspection = "false"
        log_start                          = "false"
        log_end                            = "true"
        disabled                           = "false"
        virus                              = "default"
        spyware                            = "default"
        vulnerability                      = "default"
      }
    ]
  }
  "block_rule_group" = {
    position_keyword = "bottom"
    rulebase = "pre-rulebase"
    policies_rules = [
       {
        name ="Block Some Traffic"
        tags     = [
          "Outbound",
          "Managed by Terraform"
        ]
        source_zones                       = ["Trust-L3"]
        source_addresses                   = ["10.0.0.100/32"]
        negate_source                      = "false"
        source_users                       = ["any"]
        hip_profiles                       = ["any"]
        destination_zones                  = ["any"]
        destination_addresses              = ["any"]
        negate_destination                 = "false"
        applications                       = ["ssh"]
        services                           = ["any"]
        categories                         = ["any"]
        action                             = "deny"
        disable_server_response_inspection = "false"
        log_start                          = "false"
        log_end                            = "true"
        disabled                           = "false"
      }
    ]
  }
}

NAT Policies

Security Profiles

Management profiles

variable "management_profiles" {
  description = <<-EOF
  Map of the management profiles, where key is the management profile's name:
  - `ping` - (Optional) Allow ping.
  - `telnet` - (Optional) Allow telnet.
  - `ssh` - (Optional) Allow SSH.
  - `http` - (Optional) Allow HTTP.
  - `http_ocsp` - (Optional) Allow HTTP OCSP.
  - `https` - (Optional) Allow HTTPS.
  - `snmp` - (Optional) Allow SNMP.
  - `response_pages` - (Optional) Allow response pages.
  - `userid_service` - (Optional) Allow User ID service.
  - `userid_syslog_listener_ssl` - (Optional) Allow User ID syslog listener for SSL.
  - `userid_syslog_listener_udp` - (Optional) Allow User ID syslog listener for UDP.
  - `permitted_ips` - (Optional) The list of permitted IP addresses or address ranges for this management profile.

  Example:
  {
    "mgmt_default" = {
      ping           = true
      telnet         = false
      ssh            = true
      http           = false
      https          = true
      snmp           = false
      userid_service = null
      permitted_ips  = ["1.1.1.1/32", "2.2.2.2/32"]
    }
  }
  EOF
  default     = {}
  type = map(object({
    ping                       = optional(bool)
    telnet                     = optional(bool)
    ssh                        = optional(bool)
    http                       = optional(bool)
    http_ocsp                  = optional(bool)
    https                      = optional(bool)
    snmp                       = optional(bool)
    response_pages             = optional(bool)
    userid_service             = optional(bool)
    userid_syslog_listener_ssl = optional(bool)
    userid_syslog_listener_udp = optional(bool)
    permitted_ips              = list(string)
  }))
}

Zones

variable "zones" {
  description = <<-EOF
  Map of the zones, where key is the zone's name:
  - `vsys` - The vsys (default: vsys1)
  - `mode` - (Required) The zone's mode. This can be layer3, layer2, virtual-wire, tap, or tunnel.
  - `zone_profile` - The zone protection profile.
  - `log_setting` - Log setting.
  - `enable_user_id` - Boolean to enable user identification.
  - `interfaces` - List of interfaces to associated with this zone. Leave this undefined if you want to use panos_zone_entry resources.
  - `include_acls` - Users from these addresses/subnets will be identified. This can be an address object, an address group, a single IP address, or an IP address subnet.
  - `exclude_acls` - Users from these addresses/subnets will not be identified. This can be an address object, an address group, a single IP address, or an IP address subnet.

  Example:
  {
    "default" = {}
  }
  EOF
  default     = {}
  type = map(object({
    vsys           = optional(string, "vsys1")
    mode           = optional(string)
    zone_profile   = optional(string)
    log_setting    = optional(string)
    enable_user_id = optional(bool)
    interfaces     = optional(list(string), [])
    include_acls   = optional(list(string))
    exclude_acls   = optional(list(string))
  }))
  validation {
    condition     = (length(var.zones) > 0 && alltrue([for zone in var.zones : contains(["layer3", "layer2", "virtual-wire", "tap", "tunnel"], zone.mode)]))
    error_message = "Valid types of zone's mode are `layer3`, `layer2`, `virtual-wire`, `tap`, or `tunnel``"
  }
}

Interfaces

variable "interfaces" {
  description = <<-EOF
  Map of the interfaces, where key is the interface's name:
  - `type` - (Required) Type of interface. Valid values are `ethernet`,`loopback`,`tunnel`.
  - `mode` - (Required) The interface mode. This can be any of the following values: layer3, layer2, virtual-wire, tap, ha, decrypt-mirror, or aggregate-group.
  - `zone` - (Required) The zone's name
  - `virtual_router` - (Required) The virtual router's name
  - `vsys` - (Optional) The vsys that will use this interface (default: vsys1). This should be something like vsys1 or vsys3.
  - `static_ips` - (Optional) List of static IPv4 addresses to set for this data interface.
  - `enable_dhcp` - (Optional) Set to true to enable DHCP on this interface.
  - `create_dhcp_default_route` - (Optional) Set to true to create a DHCP default route.
  - `dhcp_default_route_metric` - (Optional) The metric for the DHCP default route.
  - `ipv6_enabled` - (Optional) Set to true to enable IPv6.
  - `management_profile` - (Optional) The management profile.
  - `mtu` - (Optional) The MTU.
  - `adjust_tcp_mss` - (Optional) Adjust TCP MSS (default: false).
  - `netflow_profile - (Optional) The netflow profile.
  - `lldp_enabled` - (Optional) Enable LLDP (default: false).
  - `lldp_profile` - (Optional) LLDP profile.
  - `lldp_ha_passive_pre_negotiation` - (bool) LLDP HA passive pre-negotiation.
  - `lacp_ha_passive_pre_negotiation` - (bool) LACP HA passive pre-negotiation.
  - `link_speed` - (Optional) Link speed. This can be any of the following: 10, 100, 1000, or auto.
  - `link_duplex` - (Optional) Link duplex setting. This can be full, half, or auto.
  - `link_state` - (Optional) The link state. This can be up, down, or auto.
  - `aggregate_group` - (Optional) The aggregate group (applicable for physical firewalls only).
  - `comment` - (Optional) The interface comment.
  - `lacp_port_priority` - (int) LACP port priority.
  - `ipv4_mss_adjust` - (Optional, PAN-OS 7.1+) The IPv4 MSS adjust value.
  - `ipv6_mss_adjust` - (Optional, PAN-OS 7.1+) The IPv6 MSS adjust value.
  - `decrypt_forward` - (Optional, PAN-OS 8.1+) Enable decrypt forwarding.
  - `rx_policing_rate` - (Optional, PAN-OS 8.1+) Receive policing rate in Mbps.
  - `tx_policing_rate` - (Optional, PAN-OS 8.1+) Transmit policing rate in Mbps.
  - `dhcp_send_hostname_enable` - (Optional, PAN-OS 9.0+) For DHCP layer3 interfaces: enable sending the firewall or a custom hostname to DHCP server
  - `dhcp_send_hostname_value` - (Optional, PAN-OS 9.0+) For DHCP layer3 interfaces: the interface hostname. Leaving this unspecified with dhcp_send_hostname_enable set means to send the system hostname.

  Example:
  {
    "ethernet1/1" = {
      type                      = "ethernet"
      mode                      = "layer3"
      management_profile        = "mgmt_default"
      link_state                = "up"
      enable_dhcp               = true
      create_dhcp_default_route = false
      comment                   = "mgmt"
      virtual_router            = "default"
      zone                      = "mgmt"
      vsys                      = "vsys1"
    }
  }
  EOF
  default     = {}
  type = map(object({
    type                            = string
    mode                            = string
    zone                            = string
    virtual_router                  = string
    vsys                            = optional(string, "vsys1")
    static_ips                      = optional(list(string), [])
    enable_dhcp                     = optional(bool, false)
    create_dhcp_default_route       = optional(bool, false)
    dhcp_default_route_metric       = optional(number)
    ipv6_enabled                    = optional(bool)
    management_profile              = optional(string)
    mtu                             = optional(number)
    adjust_tcp_mss                  = optional(bool, false)
    netflow_profile                 = optional(string)
    lldp_enabled                    = optional(bool, false)
    lldp_profile                    = optional(string)
    lldp_ha_passive_pre_negotiation = optional(bool)
    lacp_ha_passive_pre_negotiation = optional(bool)
    link_speed                      = optional(string)
    link_duplex                     = optional(string)
    link_state                      = optional(string)
    aggregate_group                 = optional(string)
    comment                         = optional(string)
    lacp_port_priority              = optional(number)
    ipv4_mss_adjust                 = optional(string)
    ipv6_mss_adjust                 = optional(string)
    decrypt_forward                 = optional(bool)
    rx_policing_rate                = optional(string)
    tx_policing_rate                = optional(string)
    dhcp_send_hostname_enable       = optional(bool)
    dhcp_send_hostname_value        = optional(string)
  }))
  validation {
    condition     = (length(var.interfaces) > 0 && alltrue([for interface in var.interfaces : contains(["layer3", "layer2", "virtual-wire", "tap", "ha", "decrypt-mirror", "aggregate-group"], interface.mode)]))
    error_message = "Valid types of mode are `layer3`, `layer2`, `virtual-wire`, `tap`, `ha`, `decrypt-mirror`, or `aggregate-group`"
  }
  validation {
    condition     = (length(var.interfaces) > 0 && alltrue([for interface in var.interfaces : contains(["ethernet", "loopback", "tunnel"], interface.type)]))
    error_message = "Valid types of interfaces are `ethernet`,`loopback`,`tunnel`"
  }
  validation {
    condition     = (length(var.interfaces) > 0 && alltrue([for interface in var.interfaces : contains(["up", "down", "auto"], interface.link_state)]))
    error_message = "Valid types of link state are `up`, `down`, `auto`"
  }
  validation {
    condition     = (length(var.interfaces) > 0 && alltrue([for interface in var.interfaces : contains(["10", "100", "1000", "auto"], coalesce(interface.link_speed, "auto"))]))
    error_message = "Valid types of link speed are `10`, `100`, `1000`, or `auto`"
  }
  validation {
    condition     = (length(var.interfaces) > 0 && alltrue([for interface in var.interfaces : contains(["full", "half", "auto"], coalesce(interface.link_duplex, "auto"))]))
    error_message = "Valid types of link duplex are `full`, `half`, or `auto`"
  }
}

Virtual routers

variable "virtual_routers" {
  description = <<-EOF
  Map of the virtual routers, where key is the virtual router's name:
  - `vsys` - The vsys (default: vsys1)
  - `static_dist - (int) Admin distance - Static (default: 10).
  - `static_ipv6_dist - (int) Admin distance - Static IPv6 (default: 10).
  - `ospf_int_dist - (int) Admin distance - OSPF Int (default: 30).
  - `ospf_ext_dist - (int) Admin distance - OSPF Ext (default: 110).
  - `ospfv3_int_dist - (int) Admin distance - OSPFv3 Int (default: 30).
  - `ospfv3_ext_dist - (int) Admin distance - OSPFv3 Ext (default: 110).
  - `ibgp_dist - (int) Admin distance - IBGP (default: 200).
  - `ebgp_dist - (int) Admin distance - EBGP (default: 20).
  - `rip_dist - (int) Admin distance - RIP (default: 120).
  - `enable_ecmp - (bool) Enable ECMP.
  - `ecmp_max_path - (int) Maximum number of ECMP paths supported.
  - `ecmp_symmetric_return - (bool) Allows return packets to egress out of the ingress interface of the flow.
  - `ecmp_strict_source_path - (bool) Force VPN traffic to exit interface that the source-ip belongs to.
  - `ecmp_load_balance_method - Load balancing algorithm. Valid values are ip-modulo, ip-hash, weighted-round-robin, or balanced-round-robin.
  - `ecmp_hash_source_only - (bool) For ecmp_load_balance_method = ip-hash: Only use source address for hash.
  - `ecmp_hash_use_port - (bool) For ecmp_load_balance_method = ip-hash: Use source/destination port for hash.
  - `ecmp_hash_seed - (int) For ecmp_load_balance_method = ip-hash: User-specified hash seed.
  - `ecmp_weighted_round_robin_interfaces - (Map of ints) For ecmp_load_balance_method = weighted-round-robin: Interface weight used in weighted ECMP load balancing.

  Example:
  {
    "default" = {}
  }
  EOF
  default     = {}
  type = map(object({
    vsys                                 = optional(string, "vsys1")
    static_dist                          = optional(number, 10)
    static_ipv6_dist                     = optional(number, 10)
    ospf_int_dist                        = optional(number, 30)
    ospf_ext_dist                        = optional(number, 110)
    ospfv3_int_dist                      = optional(number, 30)
    ospfv3_ext_dist                      = optional(number, 110)
    ibgp_dist                            = optional(number, 200)
    ebgp_dist                            = optional(number, 20)
    rip_dist                             = optional(number, 120)
    enable_ecmp                          = optional(bool)
    ecmp_max_path                        = optional(number)
    ecmp_symmetric_return                = optional(bool)
    ecmp_strict_source_path              = optional(bool)
    ecmp_load_balance_method             = optional(string)
    ecmp_hash_source_only                = optional(bool)
    ecmp_hash_use_port                   = optional(bool)
    ecmp_hash_seed                       = optional(number)
    ecmp_weighted_round_robin_interfaces = optional(map(number))
  }))
  validation {
    condition     = (length(var.virtual_routers) > 0 && alltrue([for virtual_router in var.virtual_routers : contains(["ip-modulo", "ip-hash", "weighted-round-robin", "balanced-round-robin"], coalesce(virtual_router.ecmp_load_balance_method, "ip-modulo"))]))
    error_message = "Valid types of ECMP load balance method are `ip-modulo`, `ip-hash`, `weighted-round-robin`, or `balanced-round-robin`"
  }
}

Static routes


variable "static_routes" {
  description = <<-EOF
  Map of the static route, where key is the unique name e.g. build in format "{virtual_router}_{route_table}":
  - `virtual_router` - (Required) The virtual router to add the static route to.
  - `route_table` - (Optional) Target routing table to install the route. Valid values are unicast (the default), no install, multicast, or both.
  - `destination` - (Required) Destination IP address / prefix.
  - `interface` - (Optional) Interface to use.
  - `type` - (Optional) The next hop type. Valid values are ip-address (the default), discard, next-vr, or an empty string for None.
  - `next_hop` - (Optional) The value for the type setting.
  - `admin_distance` - (Optional) The admin distance.
  - `metric` - (Optional, int) Metric value / path cost (default: 10).
  - `bfd_profile` - (Optional, PAN-OS 7.1+) BFD configuration.

  Example:
  {
    "vr_default_unicast_0.0.0.0" = {
      virtual_router = "default"
      route_table    = "unicast"
      destination    = "0.0.0.0/0"
      interface      = "ethernet1/1"
      type           = "ip-address"
      next_hop       = "10.1.1.1"
      admin_distance = null
      metric         = 10
    }
  }
  EOF
  default     = {}
  type = map(object({
    virtual_router = string
    route_table    = optional(string, "unicast")
    destination    = string
    interface      = optional(string)
    type           = optional(string, "ip-address")
    next_hop       = optional(string)
    admin_distance = optional(number)
    metric         = optional(number, 10)
    bfd_profile    = optional(string)
  }))
  validation {
    condition     = (length(var.static_routes) > 0 && alltrue(flatten([for static_route in var.static_routes : contains(["unicast", "no install", "multicast", "both"], static_route.route_table)])))
    error_message = "Valid values of route tables are `unicast` (the default), `no install`, `multicast`, or `both`"
  }
  validation {
    condition     = (length(var.static_routes) > 0 && alltrue(flatten([for static_route in var.static_routes : contains(["ip-address", "discard", "next-vr", ""], static_route.type)])))
    error_message = "Valid values type in route are `ip-address` (the default), `discard`, `next-vr`, or an empty string for None"
  }
}

IPSec

variable "ike_crypto_profiles" {
  description = <<-EOF
  Map of the IKE crypto profiles, where key is the IKE crypto profile's name:
  - `dh_groups` - (Required, list) List of DH Group entries. Values should have a prefix if group.
  - `authentications` - (Required, list) List of authentication types. This c
  - `encryptions` - (Required, list) List of encryption types. Valid values are des, 3des, aes-128-cbc, aes-192-cbc, aes-256-cbc, aes-128-gcm (PAN-OS 10.0), and aes-256-gcm (PAN-OS 10.0).
  - `lifetime_type` - The lifetime type. Valid values are seconds, minutes, hours (the default), and days.
  - `lifetime_value` - (int) The lifetime value.
  - `authentication_multiple` - (PAN-OS 7.0+, int) IKEv2 SA reauthentication interval equals authetication-multiple * rekey-lifetime; 0 means reauthentication is disabled

  Example:
  {
     "AES128_default" = {
      dh_groups               = ["group2", "group5"]
      authentications         = ["md5", "sha1"]
      encryptions             = ["aes-128-cbc", "aes-192-cbc"]
      lifetime_type           = "hours"
      lifetime_value          = 24
      authentication_multiple = 0
    }
  }
  EOF
  default     = {}
  type = map(object({
    dh_groups               = list(string)
    authentications         = list(string)
    encryptions             = list(string)
    lifetime_type           = optional(string)
    lifetime_value          = optional(number)
    authentication_multiple = optional(number)
  }))
  validation {
    condition     = (length(var.ike_crypto_profiles) > 0 && alltrue([for ike_crypto_profile in var.ike_crypto_profiles : contains(["seconds", "minutes", "hours", "days"], ike_crypto_profile.lifetime_type)]))
    error_message = "Valid values for the lifetime type are `seconds`, `minutes`, `hours` (the default), and `days`"
  }
  validation {
    condition     = (length(var.ike_crypto_profiles) > 0 && alltrue([for ike_crypto_profile in var.ike_crypto_profiles : length(setsubtract(ike_crypto_profile.encryptions, ["des", "3des", "aes-128-cbc", "aes-192-cbc", "aes-256-cbc", "aes-128-gcm", "aes-256-gcm"])) == 0]))
    error_message = "Valid values for the encryptions are `des`, `3des`, `aes-128-cbc`, `aes-192-cbc`, `aes-256-cbc`, `aes-128-gcm` (PAN-OS 10.0), and `aes-256-gcm`"
  }
}

variable "ipsec_crypto_profiles" {
  description = <<-EOF
  Map of the IPSec crypto profiles, where key is the IPSec crypto profile's name:
  - `protocol` - (Optional) The protocol. Valid values are esp (the default) or ah
  - `authentications` - (Required, list) - List of authentication types.
  - `encryptions` - (Required, list) - List of encryption types. Valid values are des, 3des, aes-128-cbc, aes-192-cbc, aes-256-cbc, aes-128-gcm, aes-256-gcm, and null. Note that the "gcm" values are only available in PAN-OS 7.0+.
  - `dh_group` - (Optional) The DH group value. Valid values should start with the string group.
  - `lifetime_type` - (Optional) The lifetime type. Valid values are seconds, minutes, hours (the default), or days.
  - `lifetime_value` - (Optional, int) The lifetime value.
  - `lifesize_type` - (Optional) The lifesize type. Valid values are kb, mb, gb, or tb.
  - `lifesize_value` - (Optional, int) the lifesize value.

  Example:
  {
    "AES128_default" = {
      protocol        = "esp"
      authentications = ["md5", "sha1"]
      encryptions     = ["aes-128-cbc", "aes-192-cbc"]
      dh_group        = "group5"
      lifetime_type   = "hours"
      lifetime_value  = 24
      lifesize_type   = null
      lifesize_value  = null
    }
  }
  EOF
  default     = {}
  type = map(object({
    protocol        = optional(string, "esp")
    authentications = list(string)
    encryptions     = list(string)
    dh_group        = optional(string)
    lifetime_type   = optional(string)
    lifetime_value  = optional(number)
    lifesize_type   = optional(string)
    lifesize_value  = optional(number)
  }))
  validation {
    condition     = (length(var.ipsec_crypto_profiles) > 0 && alltrue([for ipsec_crypto_profile in var.ipsec_crypto_profiles : contains(["esp", "ah"], ipsec_crypto_profile.protocol)]))
    error_message = "Valid values for the protocol are `esp` and `ah`"
  }
  validation {
    condition     = (length(var.ipsec_crypto_profiles) > 0 && alltrue([for ipsec_crypto_profile in var.ipsec_crypto_profiles : contains(["seconds", "minutes", "hours", "days"], ipsec_crypto_profile.lifetime_type)]))
    error_message = "Valid values for the lifetime type are `seconds`, `minutes`, `hours` (the default), and `days`"
  }
  validation {
    condition     = (length(var.ipsec_crypto_profiles) > 0 && alltrue([for ipsec_crypto_profile in var.ipsec_crypto_profiles : contains(["kb", "mb", "gb", "tb"], coalesce(ipsec_crypto_profile.lifesize_type, "kb"))]))
    error_message = "Valid values for the lifesize type are `kb`, `mb`, `gb`, or `tb`"
  }
  validation {
    condition     = (length(var.ipsec_crypto_profiles) > 0 && alltrue([for ipsec_crypto_profile in var.ipsec_crypto_profiles : length(setsubtract(ipsec_crypto_profile.encryptions, ["des", "3des", "aes-128-cbc", "aes-192-cbc", "aes-256-cbc", "aes-128-gcm", "aes-256-gcm"])) == 0]))
    error_message = "Valid values for the encryptions are `des`, `3des`, `aes-128-cbc`, `aes-192-cbc`, `aes-256-cbc`, `aes-128-gcm` and `aes-256-gcm`"
  }
}

variable "ike_gateways" {
  description = <<-EOF
  Map of the IKE gateways, where key is the IKE gateway's name:
  - `version` - (Optional, PAN-OS 7.0+) The IKE gateway version. Valid values are ikev1, (the default), ikev2, or ikev2-preferred. For PAN-OS 6.1, only ikev1 is acceptable.
  - `enable_ipv6` - (Optional, PAN-OS 7.0+, bool) Enable IPv6 or not.
  - `disabled` - (Optional, PAN-OS 7.0+, bool) Set to true to disable.
  - `peer_ip_type` - (Optional) The peer IP type. Valid values are ip, dynamic, and fqdn (PANOS 8.1+).
  - `peer_ip_value` - (Optional) The peer IP value.
  - `interface` - (Required) The interface.
  - `local_ip_address_type` - (Optional) The local IP address type. Valid values for this are ip, floating-ip, or an empty string (the default) which is None.
  - `local_ip_address_value - (Optional) The IP address if local_ip_address_type is set to ip.
  - `auth_type` - (Optional) The auth type. Valid values are pre-shared-key (the default), or certificate.
  - `pre_shared_key` - (Optional) The pre-shared key value.
  - `local_id_type` - (Optional) The local ID type. Valid values are ipaddr, fqdn, ufqdn, keyid, or dn.
  - `local_id_value` - (Optional) The local ID value.
  - `peer_id_type` - (Optional) The peer ID type. Valid values are ipaddr, fqdn, ufqdn, keyid, or dn.
  - `peer_id_value` - (Optional) The peer ID value.
  - `peer_id_check` - (Optional) Enable peer ID wildcard match for certificate authentication. Valid values are exact or wildcard.
  - `local_cert` - (Optional) The local certificate name.
  - `cert_enable_hash_and_url` - (Optional, PAN-OS 7.0+, bool) Set to true to use hash-and-url for local certificate.
  - `cert_base_url` - (Optional) The host and directory part of URL for local certificates.
  - `cert_use_management_as_source` - (Optional, PAN-OS 7.0+, bool) Set to true to use management interface IP as source to retrieve http certificates
  - `cert_permit_payload_mismatch` - (Optional, bool) Set to true to permit peer identification and certificate payload identification mismatch.
  - `cert_profile` - (Optional) Profile for certificate valdiation during IKE negotiation.
  - `cert_enable_strict_validation` - (Optional, bool) Set to true to enable strict validation of peer's extended key use.
  - `enable_passive_mode` - (Optional, bool) Set to true to enable passive mode (responder only).
  - `enable_nat_traversal` - (Optional, bool) Set to true to enable NAT traversal.
  - `nat_traversal_keep_alive` - (Optional, int) Sending interval for NAT keep-alive packets (in seconds). For versions 6.1 - 8.1, this param, if specified, should be a multiple of 10 between 10 and 3600 to be valid.
  - `nat_traversal_enable_udp_checksum` - (Optional, bool) Set to true to enable NAT traversal UDP checksum.
  - `enable_fragmentation` - (Optional, bool) Set to true to enable fragmentation.
  - `ikev1_exchange_mode` - (Optional) The IKEv1 exchange mode.
  - `ikev1_crypto_profile` - (Optional) IKEv1 crypto profile.
  - `enable_dead_peer_detection` - (Optional, bool) Set to true to enable dead peer detection.
  - `dead_peer_detection_interval` - (Optional, int) The dead peer detection interval.
  - `dead_peer_detection_retry` - (Optional, int) Number of retries before disconnection.
  - `ikev2_crypto_profile` - (Optional, PAN-OS 7.0+) IKEv2 crypto profile.
  - `ikev2_cookie_validation` - (Optional, PAN-OS 7.0+) Set to true to require cookie.
  - `enable_liveness_check` - (Optional, , PAN-OS 7.0+bool) Set to true to enable sending empty information liveness check message.
  - `liveness_check_interval` - (Optional, , PAN-OS 7.0+int) Delay interval before sending probing packets (in seconds).

  Example:
  {
    "IKE-GW-1" = {
      version              = "ikev1"
      disabled             = false
      peer_ip_type         = "ip"
      peer_ip_value        = "5.5.5.5"
      interface            = "ethernet1/1"
      pre_shared_key       = "test12345"
      local_id_type        = "ipaddr"
      local_id_value       = "10.1.1.1"
      peer_id_type         = "ipaddr"
      peer_id_value        = "10.5.1.1"
      ikev1_crypto_profile = "AES128_default"
    }
  }
  EOF
  default     = {}
  type = map(object({
    version                           = optional(string)
    enable_ipv6                       = optional(bool)
    disabled                          = optional(bool)
    peer_ip_type                      = optional(string)
    peer_ip_value                     = optional(string)
    interface                         = string
    local_ip_address_type             = optional(string)
    local_ip_address_value            = optional(string)
    auth_type                         = optional(string, "pre-shared-key")
    pre_shared_key                    = optional(string)
    local_id_type                     = optional(string)
    local_id_value                    = optional(string)
    peer_id_type                      = optional(string)
    peer_id_value                     = optional(string)
    peer_id_check                     = optional(string)
    local_cert                        = optional(string)
    cert_enable_hash_and_url          = optional(bool)
    cert_base_url                     = optional(string)
    cert_use_management_as_source     = optional(bool)
    cert_permit_payload_mismatch      = optional(bool)
    cert_profile                      = optional(string)
    cert_enable_strict_validation     = optional(bool)
    enable_passive_mode               = optional(bool)
    enable_nat_traversal              = optional(bool)
    nat_traversal_keep_alive          = optional(number)
    nat_traversal_enable_udp_checksum = optional(bool)
    enable_fragmentation              = optional(bool)
    ikev1_exchange_mode               = optional(string)
    ikev1_crypto_profile              = optional(string)
    enable_dead_peer_detection        = optional(bool)
    dead_peer_detection_interval      = optional(number)
    dead_peer_detection_retry         = optional(number)
    ikev2_crypto_profile              = optional(string)
    ikev2_cookie_validation           = optional(bool)
    enable_liveness_check             = optional(bool)
    liveness_check_interval           = optional(number)
  }))
  validation {
    condition     = (length(var.ike_gateways) > 0 && alltrue([for ike_gateway in var.ike_gateways : contains(["ikev1", "ikev2", "ikev2-preferred"], ike_gateway.version)]))
    error_message = "Valid values for IKE gateway version are `ikev1`, `ikev2`, `ikev2-preferred`"
  }
  validation {
    condition     = (length(var.ike_gateways) > 0 && alltrue([for ike_gateway in var.ike_gateways : contains(["ipaddr", "fqdn", "ufqdn", "keyid", "dn"], ike_gateway.peer_id_type)]))
    error_message = "Valid values for peer ID type are `ipaddr`, `fqdn`, `ufqdn`, `keyid`, or `dn`"
  }
  validation {
    condition     = (length(var.ike_gateways) > 0 && alltrue([for ike_gateway in var.ike_gateways : contains(["ipaddr", "fqdn", "ufqdn", "keyid", "dn"], ike_gateway.local_id_type)]))
    error_message = "Valid values for local ID type are `ipaddr`, `fqdn`, `ufqdn`, `keyid`, or `dn`"
  }
  validation {
    condition     = (length(var.ike_gateways) > 0 && alltrue([for ike_gateway in var.ike_gateways : contains(["pre-shared-key", "certificate"], ike_gateway.auth_type)]))
    error_message = "Valid values for auth type are `pre-shared-key` (the default), or `certificate`"
  }
}

variable "ipsec_tunnels" {
  description = <<-EOF
  Map of the IPSec tunnels, where key is the IPSec tunnel's name:
  - `tunnel_interface` - (Required) The tunnel interface.
  - `anti_replay` - (Optional, bool) Set to true to enable Anti-Replay check on this tunnel.
  - `enable_ipv6` - (Optional, PAN-OS 7.0+, bool) Set to true to enable IPv6.
  - `copy_tos` - (Optional, bool) Set to true to copy IP TOS bits from inner packet to IPSec packet (not recommended).
  - `copy_flow_label` - (Optional, PAN-OS 7.0+, bool) Set to true to copy IPv6 flow label for 6in6 tunnel from inner packet to IPSec packet (not recommended).
  - `disabled` - (Optional, PAN-OS 7.0+, bool) Set to true to disable this IPSec tunnel.
  - `type` - (Optional) The type. Valid values are auto-key (the default), manual-key, or global-protect-satellite.
  - `ak_ike_gateway` - (Optional) IKE gateway name.
  - `ak_ipsec_crypto_profile` - (Optional) IPSec crypto profile name.
  - `mk_local_spi` - (Optional) Outbound SPI, hex format.
  - `mk_remote_spi` - (Optional) Inbound SPI, hex format.
  - `mk_local_address_ip` - (Optional) Specify exact IP address if interface has multiple addresses.
  - `mk_local_address_floating_ip` - (Optional) Floating IP address in HA Active-Active configuration.
  - `mk_protocol` - (Optional) Manual key protocol. Valid valies are esp or ah.
  - `mk_auth_type` - (Optional) Authentication algorithm. Valid values are md5, sha1, sha256, sha384, sha512, or none.
  - `mk_auth_key` - (Optional) The auth key for the given auth type.
  - `mk_esp_encryption_type` - (Optional) The encryption algorithm. Valid values are des, 3des, aes-128-cbc, aes-192-cbc, aes-256-cbc, or null.
  - `mk_esp_encryption_key` - (Optional) The encryption key.
  - `gps_interface` - (Optional) Interface to communicate with portal.
  - `gps_portal_address` - (Optional) GlobalProtect portal address.
  - `gps_prefer_ipv6` - (Optional, PAN-OS 8.0+, bool) Prefer to register the portal in IPv6. Only applicable to FQDN portal-address.
  - `gps_interface_ip_ipv4` - (Optional) specify exact IP address if interface has multiple addresses (IPv4).
  - `gps_interface_ip_ipv6` - (Optional, PAN-OS 8.0+) specify exact IP address if interface has multiple addresses (IPv6).
  - `gps_interface_floating_ip_ipv4` - (Optional, PAN-OS 7.0+) Floating IPv4 address in HA Active-Active configuration.
  - `gps_interface_floating_ip_ipv6` - (Optional, PAN-OS 8.0+) Floating IPv6 address in HA Active-Active configuration.
  - `gps_publish_connected_routes` - (Optional, bool) Set to true to to publish connected and static routes.
  - `gps_publish_routes` - (Optional, list) Specify list of routes to publish to Global Protect Gateway.
  - `gps_local_certificate` - (Optional) GlobalProtect satellite certificate file name.
  - `gps_certificate_profile` - (Optional) Profile for authenticating GlobalProtect gateway certificates.
  - `enable_tunnel_monitor` - (Optional, bool) Enable tunnel monitoring on this tunnel.
  - `tunnel_monitor_destination_ip` - (Optional) Destination IP to send ICMP probe.
  - `tunnel_monitor_source_ip` - (Optional) Source IP to send ICMP probe
  - `tunnel_monitor_profile` - (Optional) Tunnel monitor profile.
  - `tunnel_monitor_proxy_id` - (Optional, PAN-OS 7.0+) Which proxy-id (or proxy-id-v6) the monitoring traffic will use.

  Example:
  {
    "some_tunnel" = {
      virtual_router                = "internal"
      tunnel_interface              = "tunnel.42"
      type                          = "auto-key"
      disabled                      = false
      ak_ike_gateway                = "IKE-GW-1"
      ak_ipsec_crypto_profile       = "AES128_DH14"
      anti_replay                   = false
      copy_flow_label               = false
      enable_tunnel_monitor         = false
      tunnel_monitor_destination_ip = null
      tunnel_monitor_source_ip      = null
      tunnel_monitor_profile        = null
      tunnel_monitor_proxy_id       = null
      proxy_subnets                 = "example1,10.10.10.0/24,10.10.20.0/24;example2,10.10.10.0/24,10.10.30.0/24"
    }
  }
  EOF
  default     = {}
  type = map(object({
    tunnel_interface               = string
    anti_replay                    = optional(bool)
    enable_ipv6                    = optional(bool)
    copy_tos                       = optional(bool)
    copy_flow_label                = optional(bool)
    disabled                       = optional(bool)
    type                           = optional(string, "auto-key")
    ak_ike_gateway                 = optional(string)
    ak_ipsec_crypto_profile        = optional(string)
    mk_local_spi                   = optional(string)
    mk_remote_spi                  = optional(string)
    mk_local_address_ip            = optional(string)
    mk_local_address_floating_ip   = optional(string)
    mk_protocol                    = optional(string)
    mk_auth_type                   = optional(string)
    mk_auth_key                    = optional(string)
    mk_esp_encryption_type         = optional(string)
    mk_esp_encryption_key          = optional(string)
    gps_interface                  = optional(string)
    gps_portal_address             = optional(string)
    gps_prefer_ipv6                = optional(bool)
    gps_interface_ip_ipv4          = optional(string)
    gps_interface_ip_ipv6          = optional(string)
    gps_interface_floating_ip_ipv4 = optional(string)
    gps_interface_floating_ip_ipv6 = optional(string)
    gps_publish_connected_routes   = optional(bool)
    gps_publish_routes             = optional(list(string))
    gps_local_certificate          = optional(string)
    gps_certificate_profile        = optional(string)
    enable_tunnel_monitor          = optional(bool)
    tunnel_monitor_destination_ip  = optional(string)
    tunnel_monitor_source_ip       = optional(string)
    tunnel_monitor_profile         = optional(string)
    tunnel_monitor_proxy_id        = optional(string)
  }))
  validation {
    condition     = (length(var.ipsec_tunnels) > 0 && alltrue([for ipsec_tunnel in var.ipsec_tunnels : contains(["auto-key", "manual-key", "global-protect-satellite"], ipsec_tunnel.type)]))
    error_message = "Valid values for type are `auto-key` (the default), `manual-key`, or `global-protect-satellite`"
  }
  validation {
    condition     = (length(var.ipsec_tunnels) > 0 && alltrue([for ipsec_tunnel in var.ipsec_tunnels : contains(["md5", "sha1", "sha256", "sha384", "sha512", "none"], coalesce(ipsec_tunnel.mk_auth_type, "md5"))]))
    error_message = "Valid values for auth type are `md5`, `sha1`, `sha256`, `sha384`, `sha512`, or `none`"
  }
  validation {
    condition     = (length(var.ipsec_tunnels) > 0 && alltrue([for ipsec_tunnel in var.ipsec_tunnels : contains(["esp", "ah"], coalesce(ipsec_tunnel.mk_protocol, "esp"))]))
    error_message = "Valid values for the protocol are `esp` and `ah`"
  }
  validation {
    condition     = (length(var.ipsec_tunnels) > 0 && alltrue([for ipsec_tunnel in var.ipsec_tunnels : contains(["des", "3des", "aes-128-cbc", "aes-192-cbc", "aes-256-cbc"], coalesce(ipsec_tunnel.mk_esp_encryption_type, "des"))]))
    error_message = "Valid values for the encryptions are `des`, `3des`, `aes-ipsec_tunnel-cbc`, `aes-192-cbc`, `aes-256-cbc`"
  }
}

variable "ipsec_tunnels_proxy" {
  description = <<-EOF
  Map of the IPSec tunnel proxy, where key is the IPSec tunnel proxy's name:
  - `ipsec_tunnel` - (Required) The auto key IPSec tunnel to attach this proxy ID to.
  - `local` - (Optional) IP subnet or IP address represents local network.
  - `remote` - (Optional) IP subnet or IP address represents remote network.
  - `protocol_any` - (Optional, bool) Set to true for any IP protocol.
  - `protocol_number` - (Optional, int) IP protocol number.
  - `protocol_tcp_local` - (Optional, int) Local TCP port number.
  - `protocol_tcp_remote` - (Optional, int) Remote TCP port number.
  - `protocol_udp_local` - (Optional, int) Local UDP port number.
  - `protocol_udp_remote` - (Optional, int) Remote UDP port number.

  Example:
  {
    ipsec_tunnel = "some_tunnel"
  }
  EOF
  default     = {}
  type = map(object({
    ipsec_tunnel        = string
    local               = optional(string)
    remote              = optional(string)
    protocol_any        = optional(bool, true)
    protocol_number     = optional(number)
    protocol_tcp_local  = optional(number)
    protocol_tcp_remote = optional(number)
    protocol_udp_local  = optional(number)
    protocol_udp_remote = optional(number)
  }))
}

Fix issues with module network for Panorama and add integration tests for module network

Is your feature request related to a problem?

While configuring Panorama using example terraform-panos-modules/examples/csv_examples/csv_basic_example, there are problems with Ethernet interfaces configuration.

Describe the solution you'd like

Deploy example terraform-panos-modules/examples/csv_examples/csv_basic_example and fix Terraform code.

Additional context

Besides fixing problem with Ethernet interfaces configuration on Panorama, integration tests should be created.

Initial Public Release

  • Change panorama_mode variable - The variable should be renamed to panorama to avoid any ambiguity #19
  • Bump the minimum required Terraform version to 1.4 #19
  • Support destination_devices attribute in the policy module #19
  • Add a [reverse lookup map] (https://registry.terraform.io/providers/PaloAltoNetworks/panos/latest/docs/resources/administrative_tag), so the user can define colours based on the actual colour name #19
  • Add template_stack attribute support to the network module #19
  • Remove commit-push module from the initial release and move it to a draft PR #19
  • Split object module into addresses, services, tags modules #19
  • Split policy module into security_policies and nat_policies #19
  • Verify all the README files - There are some code snippets pointing to old Terraform module references
  • Enhance type validation of the variables instead of using type(any)
  • Simplify the examples to use HCL and remove json, yaml and csv examples
  • #18
  • Add basic workflow using pre-commit like in rest of our repositories (potentially reuse https://github.com/PaloAltoNetworks/terraform-modules-vmseries-ci-workflows)
  • Add release workflow (0.1.0v)

Create static routes as part of virtual_router module

Is your feature request related to a problem?

Right now the only option is to create static routes using a separate module - the user has to provide virtual_route as part of a configuration.

Describe the solution you'd like

Since static routes are always created within virtual router, it's configuration could be extended to allow to specify static routes that are part of the VR configuration and create them using panos_static_route_ipv4/panos_panorama_static_route_ipv4 within the module. In such case, vr name could be provided automatically for it's child routes, thus providing a proper dependency relation between the two.

Describe alternatives you've considered.

No response

Additional context

No response

Provide IPSEC tunnel proxy IDs as part of tunnel configuration

Is your feature request related to a problem?

Tunnel Proxies exist only within a tunnel, but are created separately.

Describe the solution you'd like

Allow to specify tunnel proxies as part of tunnel configuration to automatically provide proper dependency between the two.

Describe alternatives you've considered.

No response

Additional context

No response

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.