├── .github └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── cliargs └── cliargs.go ├── config-example.txt ├── configloader ├── config.go └── section.go ├── go.mod ├── go.sum ├── main.go ├── publicip └── ip.go └── securitygroup ├── allow.go ├── revoke.go └── rule.go /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release aws_ipadd 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v4 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v5 21 | with: 22 | go-version-file: go.mod 23 | 24 | - name: Download Dependencies 25 | run: go mod download 26 | 27 | - name: Build 28 | run: | 29 | RELEASE_VERSION="${{ github.ref_name }}" make build 30 | 31 | - name: Archive Build Artifact 32 | if: github.ref_type == 'tag' 33 | uses: actions/upload-artifact@v4 34 | with: 35 | name: aws_ipadd-artifacts 36 | path: dist/ 37 | if-no-files-found: error 38 | 39 | release: 40 | if: github.ref_type == 'tag' 41 | needs: build 42 | runs-on: ubuntu-latest 43 | 44 | steps: 45 | - name: Checkout code 46 | uses: actions/checkout@v4 47 | 48 | - name: Download Build Artifact 49 | uses: actions/download-artifact@v4 50 | with: 51 | name: aws_ipadd-artifacts 52 | path: ./ 53 | 54 | - name: Create GitHub Release 55 | uses: softprops/action-gh-release@v2 56 | with: 57 | files: | 58 | aws_ipadd_darwin_amd64.tar.gz 59 | aws_ipadd_darwin_arm64.tar.gz 60 | aws_ipadd_linux_amd64.tar.gz 61 | aws_ipadd_linux_arm64.tar.gz 62 | name: "Release ${{ github.ref_name }}" 63 | tag_name: "${{ github.ref_name }}" 64 | fail_on_unmatched_files: true 65 | env: 66 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # celery beat schedule file 95 | celerybeat-schedule 96 | 97 | # SageMath parsed files 98 | *.sage.py 99 | 100 | # Environments 101 | .env 102 | .venv 103 | env/ 104 | venv/ 105 | ENV/ 106 | env.bak/ 107 | venv.bak/ 108 | 109 | # Spyder project settings 110 | .spyderproject 111 | .spyproject 112 | 113 | # Rope project settings 114 | .ropeproject 115 | 116 | # mkdocs documentation 117 | /site 118 | 119 | # mypy 120 | .mypy_cache/ 121 | .dmypy.json 122 | dmypy.json 123 | 124 | # Pyre type checker 125 | .pyre/ 126 | 127 | .DS_Store 128 | virtualenv/ 129 | 130 | # Package File 131 | *.zip 132 | *.tar.gz 133 | aws_ipadd** 134 | 135 | # build folder 136 | dist -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Piyush Sonigra 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BUILD_DIR := dist 2 | BIN_NAME := aws_ipadd 3 | PLATFORMS := darwin/amd64 darwin/arm64 linux/amd64 linux/arm64 4 | RELEASE_VERSION ?= dev 5 | 6 | clean: 7 | @rm -rf $(BUILD_DIR) 8 | @mkdir -p $(BUILD_DIR) 9 | 10 | build: clean 11 | @for platform in $(PLATFORMS); do \ 12 | OS=$$(echo $$platform | cut -d'/' -f1); \ 13 | ARCH=$$(echo $$platform | cut -d'/' -f2); \ 14 | OUTPUT_FILE=$(BIN_NAME); \ 15 | ARTF_FILE=$(BIN_NAME)_$${OS}_$${ARCH}; \ 16 | echo "Building $$ARTF_FILE ..."; \ 17 | GOOS=$$OS GOARCH=$$ARCH go build -ldflags "-X aws_ipadd/cliargs.Version=$(RELEASE_VERSION)" -o $(BUILD_DIR)/$$OUTPUT_FILE .; \ 18 | tar -czf $(BUILD_DIR)/$$ARTF_FILE.tar.gz -C $(BUILD_DIR) $$OUTPUT_FILE; \ 19 | rm -f $(BUILD_DIR)/$$OUTPUT_FILE; \ 20 | done 21 | @echo "Build complete. Artifacts are in $(BUILD_DIR)/" 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aws_ipadd (IP add for AWS Security group) 2 | 3 | [![Build and Release aws_ipadd](https://github.com/piyushsonigra/aws_ipadd/actions/workflows/main.yml/badge.svg)](https://github.com/piyushsonigra/aws_ipadd/actions/workflows/main.yml) 4 | 5 | > **Effortlessly manage AWS security group rules with a single command** 6 | 7 | ## 📖 About 8 | 9 | `aws_ipadd` is a CLI tool that simplifies whitelisting and managing IP addresses in AWS security groups. It's designed specifically for scenarios where: 10 | 11 | - You don't have a static IP address and your public IP changes frequently 12 | - You need to maintain access to IP-restricted AWS resources 13 | - You want to grant temporary access to specific users by whitelisting their IPs 14 | - You need to maintain tight security by allowing only specific IPs to access particular ports 15 | 16 | The tool automatically detects your current public IP and updates AWS security group rules accordingly. Alternatively, you can explicitly specify IPs to whitelist without fetching your current public IP—ideal for adding team members' addresses or other trusted sources. 17 | 18 | `aws_ipadd` handles all the AWS security group rule management in the background, making IP whitelisting painless even with constantly changing IPs. 19 | 20 | ## ✨ Key Features 21 | 22 | - **Automatic IP Detection** - Detects and adds your current public IP to security groups 23 | - **Dynamic IP Management** - Updates rules when your public IP changes 24 | - **Multi-Profile Support** - Manage rules across different AWS accounts and regions 25 | - **Port Range Flexibility** - Configure single ports or port ranges 26 | - **CLI Flexibility** - Override configuration with command-line arguments 27 | - **Custom IP Support** - Specify any IP address for whitelisting instead of your current IP 28 | - **Rule Management** - Automatically handles rule creation, updates, and identification 29 | 30 | ## 🖥️ Supported Operating Systems 31 | 32 | - **macOS** (Intel x86_64 and Apple Silicon ARM64) 33 | - **Linux** (x86_64 and ARM64) 34 | 35 | ## 🚀 Installation 36 | 37 | ### Linux (x86_64/AMD64) 38 | 39 | ```console 40 | curl -s -L https://github.com/piyushsonigra/aws_ipadd/releases/latest/download/aws_ipadd_darwin_amd64.tar.gz | tar -xz -C /usr/local/bin 41 | ``` 42 | 43 | ### Linux (ARM64) 44 | 45 | ```console 46 | curl -s -L https://github.com/piyushsonigra/aws_ipadd/releases/latest/download/aws_ipadd_linux_arm64.tar.gz | tar -xz -C /usr/local/bin/ 47 | ``` 48 | 49 | ### macOS (Intel x86_64) 50 | 51 | ```console 52 | curl -s -L https://github.com/piyushsonigra/aws_ipadd/releases/latest/download/aws_ipadd_darwin_amd64.tar.gz | tar -xz -C /usr/local/bin/ 53 | ``` 54 | 55 | ### macOS (Apple Silicon ARM64) 56 | 57 | ```console 58 | curl -s -L https://github.com/piyushsonigra/aws_ipadd/releases/latest/download/aws_ipadd_darwin_arm64.tar.gz | tar -xz -C /usr/local/bin/ 59 | ``` 60 | 61 | > **Note:** If you encounter permission errors, run the command with `sudo` for tar operation as shown example below. 62 | 63 | ```console 64 | curl -s -L https://github.com/piyushsonigra/aws_ipadd/releases/latest/download/aws_ipadd_darwin_arm64.tar.gz | sudo tar -xz -C /usr/local/bin/ 65 | ``` 66 | 67 | ## ⚙️ Configuration 68 | 69 | 1. **Create configuration directory** 70 | 71 | ```console 72 | mkdir ~/.aws_ipadd 73 | ``` 74 | 75 | 2. **Create configuration file** 76 | 77 | ```console 78 | touch ~/.aws_ipadd/aws_ipadd 79 | ``` 80 | 81 | 3. **Edit the configuration file** with your security group details 82 | 83 | ### Configuration Parameters 84 | 85 | | Parameter | Description | 86 | |-----------|-------------| 87 | | `aws_profile` | AWS CLI profile name | 88 | | `region_name` | AWS region for the security group | 89 | | `security_group_id` | Target security group ID | 90 | | `rule_name` | Descriptive name for the security rule | 91 | | `protocol` | Network protocol (TCP, UDP, or 'all') | 92 | | `port` | Single port to whitelist (ignored if using port range) | 93 | | `from_port` | Start of port range (used with `to_port`) | 94 | | `to_port` | End of port range (used with `from_port`) | 95 | 96 | ### Sample Configuration 97 | 98 | ```ini 99 | # Whitelist SSH port 100 | [project-ssh] 101 | aws_profile = aws_project_profile 102 | security_group_id = sg-d26fdre9d 103 | protocol = TCP 104 | port = 22 105 | rule_name = user_name_ssh 106 | region_name = us-east-1 107 | 108 | # Whitelist port range 109 | [port-range] 110 | aws_profile = my_project 111 | security_group_id = sg-d26fdre9d 112 | protocol = TCP 113 | from_port = 3000 114 | to_port = 3005 115 | rule_name = office_ind 116 | region_name = us-east-1 117 | 118 | # Whitelist all traffic 119 | [project-all-traffic] 120 | aws_profile = project 121 | security_group_id = sg-dfg9dwe 122 | protocol = all 123 | rule_name = all_traffic_from_home 124 | region_name = us-west-2 125 | ``` 126 | 127 | ## 🔧 Usage 128 | 129 | ### Basic Usage 130 | 131 | ```console 132 | aws_ipadd --profile project-ssh 133 | ``` 134 | 135 | ### Update When IP Changes 136 | 137 | ```console 138 | $ aws_ipadd --profile project-ssh 139 | --------------- 140 | project-ssh 141 | --------------- 142 | Modifying existing rule... 143 | Removing old whitelisted IP '12.10.1.14/32'. 144 | Whitelisting new IP '131.4.10.16/32'. 145 | Rule successfully updated! 146 | ``` 147 | 148 | ### Command-Line Options 149 | 150 | ```console 151 | Usage: 152 | aws_ipadd --profile 153 | aws_ipadd --profile --port --current_ip [options] 154 | 155 | Options: 156 | --profile aws_ipadd profile name (required) 157 | --port Port number (ignored if using port range) 158 | --from_port Start of port range (use with to_port) 159 | --to_port End of port range (use with from_port) 160 | --protocol Protocol e.g tcp, udp, all 161 | --ip Custom IP address e.g '10.10.19.1/32' 162 | --rule_name Security group rule name 163 | ``` 164 | 165 | ### Specify Custom IP 166 | 167 | ```console 168 | aws_ipadd --profile project-ssh --ip=10.10.10.10/32 169 | ``` 170 | 171 | ### Automated Updates with Cron 172 | 173 | ```console 174 | # Check and update IP every 3 hours 175 | * */3 * * * /usr/local/bin/aws_ipadd --profile project-ssh 176 | ``` 177 | 178 | ## 🚀 Upcoming Features 179 | 180 | The following features are planned for future releases: 181 | 182 | - **Security Group Rule Removal** - Remove specific rules with a simple command 183 | - **Rule Listing** - View all security group rules across profiles in a clean, organized format 184 | - **IPv6 Support** - Full support for IPv6 addresses and dual-stack environments 185 | 186 | ## 📋 Use Cases 187 | 188 | - **Remote Development** - Securely access AWS resources while working from different locations 189 | - **Infrastructure Management** - Simplify access control for DevOps teams with changing IPs 190 | - **Cloud Security** - Maintain tight access controls to sensitive AWS resources 191 | - **Home Office Setup** - Keep consistent access to cloud resources with dynamically assigned ISP IPs 192 | - **Team Access Management** - Easily whitelist team members' IPs for specific resources 193 | 194 | ## 📜 License 195 | 196 | - [MIT License](https://github.com/piyushsonigra/aws_ipadd/blob/master/LICENSE) 197 | 198 | ## 🙏 Acknowledgements 199 | 200 | - [amazonaws_checkip](https://checkip.amazonaws.com) - For IP detection service 201 | -------------------------------------------------------------------------------- /cliargs/cliargs.go: -------------------------------------------------------------------------------- 1 | package cliargs 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | // Version will be set during build time 10 | var Version = "dev" 11 | 12 | // Args stores command-line arguments. 13 | type Args struct { 14 | Profile string 15 | Port string 16 | FromPort string 17 | ToPort string 18 | Protocol string 19 | IP string 20 | RuleName string 21 | } 22 | 23 | // Custom usage function for better formatting 24 | func customUsage() { 25 | fmt.Println("\nUsage:") 26 | fmt.Println(" aws_ipadd --profile ") 27 | fmt.Println(" aws_ipadd --profile --port --current_ip [options]") 28 | fmt.Println("\nOptions:") 29 | fmt.Println(" --profile aws_ipadd profile name (required)") 30 | fmt.Println(" --port Port number, this will be ignored if from_port and to_port is passed (optional)") 31 | fmt.Println(" --from_port From port number, It should be passed with to_port. Only from_port is not valid argument (optional)") 32 | fmt.Println(" --to_port To port number, It should be passed with from_port. Only to_port is not valid argument (optional)") 33 | fmt.Println(" --protocol Protocol e.g TCP, UPD, all (optional)") 34 | fmt.Println(" --ip IP address with subnetmask e.g '10.10.19.1/32' (optional)") 35 | fmt.Println(" --rule_name Security group rule name (optional)") 36 | fmt.Println(" --help, -h Show help") 37 | fmt.Println(" --version, -v Show aws_ipadd version") 38 | } 39 | 40 | // Parse CLI Arguments 41 | func ParseArgs() *Args { 42 | args := &Args{} 43 | flag.Usage = customUsage 44 | 45 | // Add version flag 46 | versionFlag := flag.Bool("version", false, "Show aws_ipadd version") 47 | // Add short version flag 48 | vShort := flag.Bool("v", false, "Show aws_ipadd version") 49 | 50 | flag.StringVar(&args.Profile, "profile", "", "AWS profile name (required)") 51 | flag.StringVar(&args.Port, "port", "", "Port number, this will be ignored if from_port and to_port is passed (optional)") 52 | flag.StringVar(&args.FromPort, "from_port", "", "Port number, It should be passed with to_port. Only from_port is not valid argument (optional)") 53 | flag.StringVar(&args.ToPort, "to_port", "", "Port number, It should be passed with from_port. Only to_port is not valid argument (optional)") 54 | flag.StringVar(&args.Protocol, "protocol", "", "Protocol e.g., TCP, UDP, all (optional)") 55 | flag.StringVar(&args.IP, "ip", "", "IP address with subnetmask e.g., '10.10.19.1/32' (optional)") 56 | flag.StringVar(&args.RuleName, "rule_name", "", "Security group rule name (optional)") 57 | 58 | flag.Parse() 59 | 60 | // Check for version 61 | if *versionFlag || *vShort { 62 | fmt.Printf("aws_ipadd version %s\n", Version) 63 | os.Exit(0) 64 | } 65 | 66 | // Check for profile 67 | if args.Profile == "" { 68 | fmt.Println("Error: --profile is required") 69 | flag.Usage() 70 | os.Exit(1) 71 | } 72 | 73 | return args 74 | } 75 | -------------------------------------------------------------------------------- /config-example.txt: -------------------------------------------------------------------------------- 1 | ## Check under configuration section in readme 2 | ## create file at /home/{user}/.aws_ipadd/aws_ipadd or you can set environment variable for custom config file path using CUSTOM_AWS_IPADD_CONFIG_FILE=./aws_ipadd 3 | 4 | [project-ssh] 5 | aws_profile = aws_project_profile 6 | security_group_id = sg-d26fdre9d 7 | protocol = TCP 8 | port = 22 9 | rule_name = user_name_ssh 10 | region_name = us-east-1 11 | 12 | [ftp] 13 | aws_profile = my_project 14 | security_group_id = sg-d26fdre9d 15 | protocol = TCP 16 | from_port = 20 17 | to_port = 21 18 | rule_name = office_ftp 19 | region_name = us-east-1 20 | 21 | [project-all-traffic] 22 | aws_profile = project 23 | security_group_id = sg-dfg9dwe 24 | protocol = all 25 | rule_name = all_traffic_from_home 26 | region_name = us-west-2 -------------------------------------------------------------------------------- /configloader/config.go: -------------------------------------------------------------------------------- 1 | package configloader 2 | 3 | import ( 4 | "aws_ipadd/cliargs" 5 | "aws_ipadd/publicip" 6 | "errors" 7 | "fmt" 8 | "slices" 9 | "strconv" 10 | "strings" 11 | 12 | "gopkg.in/ini.v1" 13 | ) 14 | 15 | // SecurityGroupRule represents the security group rule details extracted from the config file 16 | type SecurityGroupRule struct { 17 | AWSProfile string 18 | Region string 19 | SecurityGroupID string 20 | Protocol string 21 | FromPort int32 22 | ToPort int32 23 | IP string 24 | RuleName string 25 | } 26 | 27 | // GetSecurityGroupRule extracts security group rule details from the config file section 28 | func GetConfig(section *ini.Section, args *cliargs.Args) (*SecurityGroupRule, error) { 29 | 30 | rule := &SecurityGroupRule{} 31 | 32 | requiredKeys := []string{"aws_profile", "region_name", "security_group_id"} 33 | // Validate required keys 34 | for _, key := range requiredKeys { 35 | value := section.Key(key).String() 36 | if value == "" { 37 | return nil, fmt.Errorf("%s is missing in config file", key) 38 | } 39 | 40 | // Assign values dynamically based on key 41 | switch key { 42 | case "aws_profile": 43 | rule.AWSProfile = value 44 | case "region": 45 | rule.Region = value 46 | case "security_group_id": 47 | rule.SecurityGroupID = value 48 | } 49 | } 50 | 51 | var err error 52 | 53 | // Get Protocol 54 | rule.Protocol, err = getProtocolValue(section, args) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | // Get Port value 60 | rule.FromPort, rule.ToPort, err = getPortValue(section, args, rule.Protocol) 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | // Get IP value 66 | rule.IP, err = getIPvalue(args) 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | // Set rule name 72 | if args.RuleName != "" { 73 | rule.RuleName = strings.TrimSpace(args.RuleName) 74 | } else { 75 | rule.RuleName = strings.TrimSpace(section.Key("rule_name").String()) 76 | } 77 | 78 | return rule, nil 79 | } 80 | 81 | // port value 82 | func getPortValue(section *ini.Section, args *cliargs.Args, protocol string) (int32, int32, error) { 83 | // Extract values from config and CLI arguments 84 | cfgPort := section.Key("port").String() 85 | cfgFromPort := section.Key("from_port").String() 86 | cfgToPort := section.Key("to_port").String() 87 | argPort := args.Port 88 | argFromPort := args.FromPort 89 | argToPort := args.ToPort 90 | 91 | // Check for all traffic rule 92 | // For Protocol value "all", port is not required to pass, all protocols and ports are allowed for whitelisted IP 93 | if protocol == "all" { 94 | return 0, 0, nil 95 | } 96 | 97 | // Ensure at least one of the port values is provided 98 | if cfgPort == "" && cfgFromPort == "" && cfgToPort == "" && argPort == "" && argFromPort == "" && argToPort == "" { 99 | return 0, 0, errors.New("port or from_port and to_port must be provided either in the config file or CLI arguments") 100 | } 101 | 102 | // Case 1: Use from_port and to_port from CLI arguments if both are provided 103 | if argFromPort != "" && argToPort != "" { 104 | fromPort, err := parsePort(argFromPort) 105 | if err != nil { 106 | return 0, 0, fmt.Errorf("invalid 'from_port' value in CLI arguments: %v", err) 107 | } 108 | toPort, err := parsePort(argToPort) 109 | if err != nil { 110 | return 0, 0, fmt.Errorf("invalid 'to_port' value in CLI arguments: %v", err) 111 | } 112 | return fromPort, toPort, nil 113 | } 114 | 115 | // Case 2: Use port from CLI arguments if provided 116 | if argPort != "" { 117 | port, err := parsePort(argPort) 118 | if err != nil { 119 | return 0, 0, fmt.Errorf("invalid 'port' value in CLI arguments: %v", err) 120 | } 121 | return port, port, nil 122 | } 123 | 124 | // Case 3: Use from_port and to_port from config file if both exist 125 | if cfgFromPort != "" && cfgToPort != "" { 126 | fromPort, err := parsePort(cfgFromPort) 127 | if err != nil { 128 | return 0, 0, fmt.Errorf("invalid 'from_port' value in config file: %v", err) 129 | } 130 | toPort, err := parsePort(cfgToPort) 131 | if err != nil { 132 | return 0, 0, fmt.Errorf("invalid 'to_port' value in config file: %v", err) 133 | } 134 | return fromPort, toPort, nil 135 | } 136 | 137 | // Case 4: Use port from config file if available 138 | if cfgPort != "" { 139 | port, err := parsePort(cfgPort) 140 | if err != nil { 141 | return 0, 0, fmt.Errorf("invalid 'port' value in config file: %v", err) 142 | } 143 | return port, port, nil 144 | } 145 | 146 | // This should never be reached, but return an error just in case 147 | return 0, 0, errors.New("unexpected error determining port values") 148 | } 149 | 150 | // parsePort converts a port string to an int32 value 151 | func parsePort(portStr string) (int32, error) { 152 | port, err := strconv.ParseInt(portStr, 10, 32) 153 | return int32(port), err 154 | } 155 | 156 | // IP value 157 | func getIPvalue(args *cliargs.Args) (string, error) { 158 | 159 | // Check if IP exist in cli arguments 160 | ip := args.IP 161 | var err error 162 | if ip == "" { 163 | ip, err = publicip.GetCurrentPublicIP() 164 | if err != nil { 165 | return "", err 166 | } 167 | return ip, nil 168 | } 169 | return ip, nil 170 | } 171 | 172 | // Get protocol value 173 | func getProtocolValue(section *ini.Section, args *cliargs.Args) (string, error) { 174 | // Extract protocol from config file and CLI arguments, prioritizing CLI input 175 | protocol := strings.ToLower(args.Protocol) 176 | if protocol == "" { 177 | protocol = strings.ToLower(section.Key("protocol").String()) 178 | } 179 | 180 | // Validate if protocol is provided 181 | if protocol == "" { 182 | return "", errors.New("protocol value is missing in both config file and CLI arguments") 183 | } 184 | 185 | // Define allowed protocols 186 | allowedProtocols := []string{"tcp", "udp", "all"} 187 | 188 | // Check if the provided protocol is valid 189 | if !slices.Contains(allowedProtocols, protocol) { 190 | return "", fmt.Errorf("invalid protocol: %s, valid values are: tcp, udp, all", protocol) 191 | } 192 | 193 | return protocol, nil 194 | } 195 | -------------------------------------------------------------------------------- /configloader/section.go: -------------------------------------------------------------------------------- 1 | package configloader 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "gopkg.in/ini.v1" 8 | ) 9 | 10 | // Constants for AWS IP check URL and configuration file path 11 | const ( 12 | configFilePath = "%s/.aws_ipadd/aws_ipadd" 13 | ) 14 | 15 | // Get config file from env var or default 16 | func getConfigFile() string { 17 | // Check if a custom config file path is provided via environment variable 18 | customPath := os.Getenv("CUSTOM_AWS_IPADD_CONFIG_FILE") 19 | if customPath != "" { 20 | // If the custom path is set, return it as-is 21 | return customPath 22 | } 23 | // Fallback to the default configuration path 24 | return fmt.Sprintf(configFilePath, os.Getenv("HOME")) 25 | } 26 | 27 | // Get profile section from config file 28 | func GetSection(profile string) (*ini.Section, error) { 29 | configFilePath := getConfigFile() 30 | loadConfig, err := ini.Load(configFilePath) 31 | if err != nil { 32 | return nil, fmt.Errorf("failed to read config file: \n%v", err) 33 | } 34 | 35 | // Check if profile exists in config file 36 | if !loadConfig.HasSection(profile) { 37 | return nil, fmt.Errorf("profile \"%s\" doesn't exist in \"%s\"", profile, configFilePath) 38 | } 39 | 40 | section := loadConfig.Section(profile) 41 | return section, nil 42 | } 43 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module aws_ipadd 2 | 3 | go 1.23.4 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go-v2 v1.36.2 7 | github.com/aws/aws-sdk-go-v2/config v1.29.7 8 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.203.1 9 | gopkg.in/ini.v1 v1.67.0 10 | ) 11 | 12 | require ( 13 | github.com/aws/aws-sdk-go-v2/credentials v1.17.60 // indirect 14 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.29 // indirect 15 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33 // indirect 16 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33 // indirect 17 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect 18 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect 19 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.14 // indirect 20 | github.com/aws/aws-sdk-go-v2/service/sso v1.24.16 // indirect 21 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.15 // indirect 22 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.15 // indirect 23 | github.com/aws/smithy-go v1.22.2 // indirect 24 | github.com/stretchr/testify v1.10.0 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/aws/aws-sdk-go-v2 v1.36.2 h1:Ub6I4lq/71+tPb/atswvToaLGVMxKZvjYDVOWEExOcU= 2 | github.com/aws/aws-sdk-go-v2 v1.36.2/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= 3 | github.com/aws/aws-sdk-go-v2/config v1.29.7 h1:71nqi6gUbAUiEQkypHQcNVSFJVUFANpSeUNShiwWX2M= 4 | github.com/aws/aws-sdk-go-v2/config v1.29.7/go.mod h1:yqJQ3nh2HWw/uxd56bicyvmDW4KSc+4wN6lL8pYjynU= 5 | github.com/aws/aws-sdk-go-v2/credentials v1.17.60 h1:1dq+ELaT5ogfmqtV1eocq8SpOK1NRsuUfmhQtD/XAh4= 6 | github.com/aws/aws-sdk-go-v2/credentials v1.17.60/go.mod h1:HDes+fn/xo9VeszXqjBVkxOo/aUy8Mc6QqKvZk32GlE= 7 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.29 h1:JO8pydejFKmGcUNiiwt75dzLHRWthkwApIvPoyUtXEg= 8 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.29/go.mod h1:adxZ9i9DRmB8zAT0pO0yGnsmu0geomp5a3uq5XpgOJ8= 9 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33 h1:knLyPMw3r3JsU8MFHWctE4/e2qWbPaxDYLlohPvnY8c= 10 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.33/go.mod h1:EBp2HQ3f+XCB+5J+IoEbGhoV7CpJbnrsd4asNXmTL0A= 11 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33 h1:K0+Ne08zqti8J9jwENxZ5NoUyBnaFDTu3apwQJWrwwA= 12 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.33/go.mod h1:K97stwwzaWzmqxO8yLGHhClbVW1tC6VT1pDLk1pGrq4= 13 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= 14 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= 15 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.203.1 h1:ZgY9zeVAe+54Qa7o1GXKRNTez79lffCeJSSinhl+qec= 16 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.203.1/go.mod h1:0naMk66LtdeTmE+1CWQTKwtzOQ2t8mavOhMhR0Pv1m0= 17 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE= 18 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= 19 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.14 h1:2scbY6//jy/s8+5vGrk7l1+UtHl0h9A4MjOO2k/TM2E= 20 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.14/go.mod h1:bRpZPHZpSe5YRHmPfK3h1M7UBFCn2szHzyx0rw04zro= 21 | github.com/aws/aws-sdk-go-v2/service/sso v1.24.16 h1:YV6xIKDJp6U7YB2bxfud9IENO1LRpGhe2Tv/OKtPrOQ= 22 | github.com/aws/aws-sdk-go-v2/service/sso v1.24.16/go.mod h1:DvbmMKgtpA6OihFJK13gHMZOZrCHttz8wPHGKXqU+3o= 23 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.15 h1:kMyK3aKotq1aTBsj1eS8ERJLjqYRRRcsmP33ozlCvlk= 24 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.15/go.mod h1:5uPZU7vSNzb8Y0dm75xTikinegPYK3uJmIHQZFq5Aqo= 25 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.15 h1:ht1jVmeeo2anR7zDiYJLSnRYnO/9NILXXu42FP3rJg0= 26 | github.com/aws/aws-sdk-go-v2/service/sts v1.33.15/go.mod h1:xWZ5cOiFe3czngChE4LhCBqUxNwgfwndEF7XlYP/yD8= 27 | github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= 28 | github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= 29 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 30 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 31 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 32 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 33 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 34 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 35 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 36 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 37 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 38 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 39 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "aws_ipadd/cliargs" 5 | "aws_ipadd/configloader" 6 | "aws_ipadd/securitygroup" 7 | "log" 8 | ) 9 | 10 | func main() { 11 | 12 | // Get CLI args 13 | args := cliargs.ParseArgs() 14 | 15 | // Load profile config from file 16 | section, err := configloader.GetSection(args.Profile) 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | 21 | // Prepare security group rule 22 | rule, err := configloader.GetConfig(section, args) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | // Process security group rule 28 | _, err = securitygroup.ProcessRule(args.Profile, rule) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /publicip/ip.go: -------------------------------------------------------------------------------- 1 | package publicip 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | awsIPCheckURL = "https://checkip.amazonaws.com" 12 | ) 13 | 14 | // Retrieves the current public IP address of the machine 15 | func GetCurrentPublicIP() (string, error) { 16 | // Query AWS IP check service 17 | resp, err := http.Get(awsIPCheckURL) 18 | if err != nil { 19 | return "", err 20 | } 21 | defer resp.Body.Close() 22 | 23 | // Read and format response 24 | body, err := io.ReadAll(resp.Body) 25 | if err != nil { 26 | return "", err 27 | } 28 | return fmt.Sprintf("%s/32", strings.TrimSpace(string(body))), nil 29 | } 30 | -------------------------------------------------------------------------------- /securitygroup/allow.go: -------------------------------------------------------------------------------- 1 | package securitygroup 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/aws/aws-sdk-go-v2/service/ec2" 8 | "github.com/aws/aws-sdk-go-v2/service/ec2/types" 9 | ) 10 | 11 | // Allows a new IP permission in the security group 12 | func allowIPPermission(client *ec2.Client, securityGroupID *string, rule types.IpPermission, newIP *string) (string, error) { 13 | 14 | // Update rule with the new IP to be allowed 15 | rule.IpRanges[0].CidrIp = newIP 16 | input := &ec2.AuthorizeSecurityGroupIngressInput{ 17 | GroupId: securityGroupID, 18 | IpPermissions: []types.IpPermission{rule}, 19 | } 20 | 21 | // Execute the allow operation 22 | _, err := client.AuthorizeSecurityGroupIngress(context.TODO(), input) 23 | if err != nil { 24 | return "", err 25 | } 26 | 27 | resFmt := fmt.Sprintf("Whitelisted your IP %s for FromPort %d to ToPort %d.", *rule.IpRanges[0].CidrIp, *rule.FromPort, *rule.ToPort) 28 | if *rule.FromPort == 0 && *rule.ToPort == 0 { 29 | resFmt = fmt.Sprintf("Whitelisted your IP %s for all traffic.", *rule.IpRanges[0].CidrIp) 30 | } 31 | 32 | return resFmt, nil 33 | } 34 | -------------------------------------------------------------------------------- /securitygroup/revoke.go: -------------------------------------------------------------------------------- 1 | package securitygroup 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/aws/aws-sdk-go-v2/aws" 8 | "github.com/aws/aws-sdk-go-v2/service/ec2" 9 | "github.com/aws/aws-sdk-go-v2/service/ec2/types" 10 | ) 11 | 12 | // RevokeIPPermission removes an existing security group rule 13 | func revokeIPPermission(client *ec2.Client, securityGroupID *string, rule types.IpPermission, oldIP string) (string, error) { 14 | 15 | // Update rule with the new IP to be allowed 16 | rule.IpRanges[0].CidrIp = &oldIP 17 | input := &ec2.RevokeSecurityGroupIngressInput{ 18 | GroupId: securityGroupID, 19 | IpPermissions: []types.IpPermission{rule}, 20 | } 21 | 22 | res, err := client.RevokeSecurityGroupIngress(context.TODO(), input) 23 | if err != nil { 24 | return "", fmt.Errorf("failed to revoke IP: %v", err) 25 | } 26 | 27 | // Handling UnknownIpPermissions 28 | if len(res.UnknownIpPermissions) > 0 { 29 | fmt.Println("Unknown IP Permissions detected. The following rules were not found:") 30 | for _, perm := range res.UnknownIpPermissions { 31 | fmt.Printf(" Protocol: %s, Port: %d-%d", aws.ToString(perm.IpProtocol), aws.ToInt32(perm.FromPort), aws.ToInt32(perm.ToPort)) 32 | for _, ipRange := range perm.IpRanges { 33 | fmt.Printf(", CIDR: %s, Description: %s\n", aws.ToString(ipRange.CidrIp), aws.ToString(ipRange.Description)) 34 | } 35 | } 36 | return "", fmt.Errorf("failed") 37 | } 38 | 39 | resFmt := fmt.Sprintf("Removed old whitelisted IP %s for FromPort %d to ToPort %d", oldIP, *rule.FromPort, *rule.ToPort) 40 | if *rule.FromPort == 0 && *rule.ToPort == 0 { 41 | resFmt = fmt.Sprintf("Removed old whitelisted IP %s for all traffic", oldIP) 42 | } 43 | 44 | return resFmt, nil 45 | } 46 | -------------------------------------------------------------------------------- /securitygroup/rule.go: -------------------------------------------------------------------------------- 1 | package securitygroup 2 | 3 | import ( 4 | "aws_ipadd/configloader" 5 | "context" 6 | "fmt" 7 | 8 | "github.com/aws/aws-sdk-go-v2/aws" 9 | "github.com/aws/aws-sdk-go-v2/config" 10 | "github.com/aws/aws-sdk-go-v2/service/ec2" 11 | "github.com/aws/aws-sdk-go-v2/service/ec2/types" 12 | ) 13 | 14 | // Processes security group rules 15 | func ProcessRule(profile string, ruleConfig *configloader.SecurityGroupRule) (string, error) { 16 | fmt.Printf("---------------\n%s\n---------------\nSecurityGroupID: %s\n", profile, ruleConfig.SecurityGroupID) 17 | 18 | // Get security group rules 19 | ec2Client, securityGroupRules, err := getSecurityGroupRules(ruleConfig) 20 | if err != nil { 21 | return "", err 22 | } 23 | 24 | // Get valid allowed rules 25 | validRules, err := getValidRules(ruleConfig, &securityGroupRules) 26 | if err != nil { 27 | return "", err 28 | } 29 | 30 | // findMatchingRule searches for matching rules by IP or rule name 31 | ruleIPMatched, ruleNameMatched, matchingRule := getMatchingRule(ruleConfig, &validRules) 32 | 33 | // Return if IP matched in rule 34 | if ruleIPMatched { 35 | resFmt := fmt.Sprintf("Your IP %s is already whitelisted for FromPort %d to ToPort %d.\n", ruleConfig.IP, ruleConfig.FromPort, ruleConfig.ToPort) 36 | if ruleConfig.Protocol == "all" { 37 | resFmt = fmt.Sprintf("Your IP %s is already whitelisted for all traffic.\n", ruleConfig.IP) 38 | } 39 | fmt.Print(resFmt) 40 | return "", nil 41 | } 42 | 43 | // Define the new security group rule with the current IP 44 | newRule := types.IpPermission{ 45 | IpProtocol: aws.String(ruleConfig.Protocol), 46 | FromPort: aws.Int32(ruleConfig.FromPort), 47 | ToPort: aws.Int32(ruleConfig.ToPort), 48 | IpRanges: []types.IpRange{ 49 | { 50 | CidrIp: aws.String(ruleConfig.IP), 51 | Description: aws.String(ruleConfig.RuleName), 52 | }, 53 | }, 54 | } 55 | 56 | // Create rule if there is no matching rule for requested port 57 | if !ruleIPMatched && !ruleNameMatched { 58 | res, err := allowIPPermission(ec2Client, &ruleConfig.SecurityGroupID, newRule, &ruleConfig.IP) 59 | if err != nil { 60 | return "", err 61 | } 62 | fmt.Println(res) 63 | return "", nil 64 | } 65 | 66 | // Modify security group rule 67 | if ruleNameMatched { 68 | // Revoke old IP permission 69 | if ruleConfig.RuleName != "" { 70 | fmt.Println("Updating your current IP...") 71 | res, err := revokeIPPermission(ec2Client, &ruleConfig.SecurityGroupID, newRule, *matchingRule.IpRanges[0].CidrIp) 72 | if err != nil { 73 | return "", err 74 | } 75 | fmt.Println(res) 76 | } 77 | 78 | // Allow new IP permission 79 | res, err := allowIPPermission(ec2Client, &ruleConfig.SecurityGroupID, newRule, &ruleConfig.IP) 80 | if err != nil { 81 | return "", err 82 | } 83 | fmt.Println(res) 84 | 85 | return "", nil 86 | } 87 | 88 | fmt.Println("Nothing to process") 89 | return "", nil 90 | } 91 | 92 | // Get security group rules list 93 | func getSecurityGroupRules(ruleConfig *configloader.SecurityGroupRule) (*ec2.Client, []types.IpPermission, error) { 94 | 95 | // Load AWS SDK configuration with specified profile and region 96 | awsCfg, err := config.LoadDefaultConfig(context.TODO(), 97 | config.WithSharedConfigProfile(ruleConfig.AWSProfile), 98 | config.WithRegion(ruleConfig.Region), 99 | ) 100 | if err != nil { 101 | return nil, nil, err 102 | } 103 | 104 | // Create EC2 client and retrieve security group details 105 | ec2Client := ec2.NewFromConfig(awsCfg) 106 | describeInput := &ec2.DescribeSecurityGroupsInput{ 107 | GroupIds: []string{ruleConfig.SecurityGroupID}, 108 | } 109 | securityGroup, err := ec2Client.DescribeSecurityGroups(context.TODO(), describeInput) 110 | if err != nil { 111 | return nil, nil, err 112 | } 113 | return ec2Client, securityGroup.SecurityGroups[0].IpPermissions, nil 114 | } 115 | 116 | // Filter Valid rules for the allowed port and protocol 117 | func getValidRules(ruleConfig *configloader.SecurityGroupRule, securityGroupRules *[]types.IpPermission) ([]types.IpPermission, error) { 118 | var matchingRules []types.IpPermission 119 | for _, rule := range *securityGroupRules { 120 | protocol := derefString(rule.IpProtocol) // Convert *string to string safely 121 | 122 | switch { 123 | case protocol == "-1": 124 | // "all" protocol means all traffic, represented by "-1" in AWS security rules 125 | if ruleConfig.Protocol == "all" { 126 | matchingRules = append(matchingRules, rule) 127 | return matchingRules, nil 128 | } 129 | 130 | case protocol == "tcp" || protocol == "udp": 131 | // Validate TCP and UDP rules against ruleConfig 132 | if protocol == ruleConfig.Protocol && 133 | derefInt(rule.FromPort) == ruleConfig.FromPort && 134 | derefInt(rule.ToPort) == ruleConfig.ToPort { 135 | 136 | matchingRules = append(matchingRules, rule) 137 | return matchingRules, nil 138 | } 139 | 140 | default: 141 | return nil, fmt.Errorf("unable to handle protocol %s, valid values are tcp, udp, all", ruleConfig.Protocol) 142 | } 143 | } 144 | return matchingRules, nil 145 | } 146 | 147 | // Get matching rule for IP and Rule name 148 | func getMatchingRule(ruleConfig *configloader.SecurityGroupRule, validRules *[]types.IpPermission) (bool, bool, types.IpPermission) { 149 | var matchingRule types.IpPermission 150 | var ruleIPMatched, ruleNameMatched bool 151 | 152 | for _, rule := range *validRules { 153 | for _, ipRange := range rule.IpRanges { 154 | matchingRule = rule 155 | if derefString(ipRange.CidrIp) == ruleConfig.IP { 156 | ruleIPMatched = true 157 | matchingRule.IpRanges = []types.IpRange{ipRange} 158 | } 159 | if derefString(ipRange.Description) == ruleConfig.RuleName { 160 | ruleNameMatched = true 161 | matchingRule.IpRanges = []types.IpRange{ipRange} 162 | } 163 | } 164 | if ruleIPMatched || ruleNameMatched { 165 | break 166 | } 167 | } 168 | return ruleIPMatched, ruleNameMatched, matchingRule 169 | } 170 | 171 | // Helper function to safely dereference an *int pointer 172 | // If the pointer is nil, return 0 to avoid runtime panics 173 | func derefInt(p *int32) int32 { 174 | if p != nil { 175 | return *p 176 | } 177 | return 0 178 | } 179 | 180 | // Helper function to safely dereference a *string pointer 181 | // If the pointer is nil, return an empty string 182 | func derefString(p *string) string { 183 | if p != nil { 184 | return *p 185 | } 186 | return "" 187 | } 188 | --------------------------------------------------------------------------------