diff --git a/.ansible-lint-ignore b/.ansible-lint-ignore new file mode 100644 index 0000000..d025a99 --- /dev/null +++ b/.ansible-lint-ignore @@ -0,0 +1 @@ +galaxy.yml galaxy[version-incorrect] \ No newline at end of file diff --git a/.cz.yaml b/.cz.yaml new file mode 100644 index 0000000..54f4ce1 --- /dev/null +++ b/.cz.yaml @@ -0,0 +1,8 @@ +--- +commitizen: + name: cz_conventional_commits + prerelease_offset: 1 + tag_format: $version + update_changelog_on_bump: false + version: 0.0.1 + version_scheme: semver diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e1cf0ed --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +.vscode/* +artifacts/* +cache/* +docs/* +extensions/* +gitlab-ci/* +meta/* +playbooks/* +plugins/* +roles/* +website-template/* +.ansible-lint-ignore +.cz.yaml +.gitignore +.gitlab* +.gitmodules +.nfc_automation.yaml +*.md +galaxy.yml +LICENSE +mkdocs.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7de001c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +artifacts/* +cache/* +**__pycache__/ + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..50a16e8 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,281 @@ + +variables: + ANSIBLE_GALAXY_PACKAGE_NAME: phpipam_scanagent + GIT_SYNC_URL: "https://$GITHUB_USERNAME_ROBOT:$GITHUB_TOKEN_ROBOT@github.com/NoFussComputing/ansible_collection_phpipam_scan_agent.git" + # GIT_SUBMODULE_PATHS: gitlab-ci + GIT_SUBMODULE_DEPTH: 1 + + # Docs NFC + PAGES_ENVIRONMENT_PATH: "projects/ansible/collection/$CI_PROJECT_NAME" + + # Docker Build / Publish + DOCKER_IMAGE_BUILD_TARGET_PLATFORMS: "linux/amd64,linux/arm64" + DOCKER_IMAGE_BUILD_NAME: phpipam-scan-agent + DOCKER_IMAGE_BUILD_REGISTRY: $CI_REGISTRY_IMAGE + DOCKER_IMAGE_BUILD_TAG: $CI_COMMIT_SHA + + # Docker Publish + DOCKER_IMAGE_PUBLISH_NAME: phpipam-scan-agent + DOCKER_IMAGE_PUBLISH_REGISTRY: docker.io/nofusscomputing + DOCKER_IMAGE_PUBLISH_URL: https://hub.docker.com/r/nofusscomputing/$DOCKER_IMAGE_PUBLISH_NAME + + +include: + - project: nofusscomputing/projects/gitlab-ci + ref: development + file: + - .gitlab-ci_common.yaml + - template/ansible-collection.gitlab-ci.yaml + - template/mkdocs-documentation.gitlab-ci.yaml + # ToDo: update gitlabCI jobs for collections workflow + - git_push_mirror/.gitlab-ci.yml + + +Github (Push --mirror): + extends: + - .git_push_mirror + needs: [] + + +.build_docker_container: + stage: prepare + image: + name: nofusscomputing/docker-buildx-qemu:dev + pull_policy: always + services: + - name: docker:23-dind + entrypoint: ["env", "-u", "DOCKER_HOST"] + command: ["dockerd-entrypoint.sh"] + variables: + DOCKER_HOST: tcp://docker:2375/ + DOCKER_DRIVER: overlay2 + DOCKER_DOCKERFILE: dockerfile + # See https://github.com/docker-library/docker/pull/166 + DOCKER_TLS_CERTDIR: "" + # DOCKER_BUILD_ARGS: # Optional + before_script: + - git submodule foreach git submodule update --init + - docker info + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + - update-binfmts --display + - update-binfmts --enable # Important: Ensures execution of other binary formats is enabled in the kernel + - docker buildx create --driver=docker-container --driver-opt image=moby/buildkit:v0.11.6 --use + - docker buildx inspect --bootstrap + script: + - update-binfmts --display + - | + echo "[DEBUG] building multiarch/specified arch image"; + + if [ ${CI_COMMIT_TAG} ]; then + + export DOCKER_BUILD_ARGS="$DOCKER_BUILD_ARGS --build-arg COLLECTION_PACKAGE=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${CI_PROJECT_NAME}/${CI_COMMIT_TAG}/${ANSIBLE_GALAXY_NAMESPACE}-${ANSIBLE_GALAXY_PACKAGE_NAME}-${CI_COMMIT_TAG}.tar.gz" + + echo "Trace Updated DOCKER_BUILD_ARGS[$DOCKER_BUILD_ARGS]"; + + fi; + + docker buildx build --platform=$DOCKER_IMAGE_BUILD_TARGET_PLATFORMS . \ + --label org.opencontainers.image.created="$(date '+%Y-%m-%d %H:%M:%S%:z')" \ + --label org.opencontainers.image.documentation="https://nofusscomputing/$PAGES_ENVIRONMENT_PATH/" \ + --label org.opencontainers.image.source="$CI_PROJECT_URL" \ + --label org.opencontainers.image.revision="$CI_COMMIT_SHA" \ + --push \ + --build-arg COLLECTION_BRANCH=$CI_COMMIT_BRANCH --build-arg COLLECTION_COMMIT=$CI_COMMIT_SHA \ + $DOCKER_BUILD_ARGS \ + --build-arg CI_JOB_TOKEN=$CI_JOB_TOKEN --build-arg CI_PROJECT_ID=$CI_PROJECT_ID --build-arg CI_API_V4_URL=$CI_API_V4_URL \ + --file $DOCKER_DOCKERFILE \ + --tag $DOCKER_IMAGE_BUILD_REGISTRY/$DOCKER_IMAGE_BUILD_NAME:$DOCKER_IMAGE_BUILD_TAG; + + docker buildx imagetools inspect $DOCKER_IMAGE_BUILD_REGISTRY/$DOCKER_IMAGE_BUILD_NAME:$DOCKER_IMAGE_BUILD_TAG; + + # during docker multi platform build there are >=3 additional unknown images added to gitlab container registry. cleanup + + DOCKER_MULTI_ARCH_IMAGES=$(docker buildx imagetools inspect "$DOCKER_IMAGE_BUILD_REGISTRY/$DOCKER_IMAGE_BUILD_NAME:$DOCKER_IMAGE_BUILD_TAG" --format "{{ range .Manifest.Manifests }}{{ if ne (print .Platform) \"&{unknown unknown [] }\" }}$DOCKER_IMAGE_BUILD_REGISTRY/$DOCKER_IMAGE_BUILD_NAME:$DOCKER_IMAGE_BUILD_TAG@{{ println .Digest }}{{end}} {{end}}"); + + docker buildx imagetools create $DOCKER_MULTI_ARCH_IMAGES --tag $DOCKER_IMAGE_BUILD_REGISTRY/$DOCKER_IMAGE_BUILD_NAME:$DOCKER_IMAGE_BUILD_TAG; + + docker buildx imagetools inspect $DOCKER_IMAGE_BUILD_REGISTRY/$DOCKER_IMAGE_BUILD_NAME:$DOCKER_IMAGE_BUILD_TAG; + + # Declare rules with job + # rules: + + # - if: $CI_COMMIT_TAG + # when: on_success + + # - if: "$CI_COMMIT_AUTHOR =='nfc_bot '" + # when: never + + # - if: # Occur on merge + # $CI_COMMIT_BRANCH == "development" + # && + # $CI_PIPELINE_SOURCE == "push" + # when: always + + # - if: + # $CI_COMMIT_BRANCH != "development" + # && + # $CI_COMMIT_BRANCH != "master" + # && + # $CI_PIPELINE_SOURCE == "push" + # when: always + + + # - when: never + + +.publish-docker-hub: + stage: publish + image: docker:23-dind + services: + - docker:23-dind + variables: + GIT_STRATEGY: none + before_script: + - | + + docker login $CI_REGISTRY -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD; + + for i in ${DOCKER_IMAGE_BUILD_TARGET_PLATFORMS//,/ } + do + + docker buildx imagetools inspect $DOCKER_IMAGE_BUILD_REGISTRY/$DOCKER_IMAGE_BUILD_NAME:$DOCKER_IMAGE_BUILD_TAG; + + DOCKER_MULTI_ARCH_IMAGES=$(docker buildx imagetools inspect "$DOCKER_IMAGE_BUILD_REGISTRY/$DOCKER_IMAGE_BUILD_NAME:$DOCKER_IMAGE_BUILD_TAG" --format "{{ range .Manifest.Manifests }}$DOCKER_IMAGE_BUILD_REGISTRY/$DOCKER_IMAGE_BUILD_NAME:$DOCKER_IMAGE_BUILD_TAG@{{ println .Digest }} {{end}}") + + echo "[DEBUG] DOCKER_MULTI_ARCH_IMAGES=$DOCKER_MULTI_ARCH_IMAGES"; + + done; + + script: + - docker login docker.io -u $NFC_DOCKERHUB_USERNAME -p $NFC_DOCKERHUB_TOKEN + - docker image ls + - | + DOCKER_HUB_TAG=dev + + echo "[DEBUG] default: DOCKER_HUB_TAG=$DOCKER_HUB_TAG"; + + if [ "$CI_COMMIT_BRANCH" == "master" ]; then + + DOCKER_HUB_TAG=latest + + echo "[DEBUG] stable: DOCKER_HUB_TAG=$DOCKER_HUB_TAG"; + + elif [ "$CI_COMMIT_BRANCH" == "development" ]; then + + DOCKER_HUB_TAG=dev + + echo "[DEBUG] dev: DOCKER_HUB_TAG=$DOCKER_HUB_TAG"; + + fi; + + echo "[DEBUG] DOCKER_IMAGE_PUBLISH_NAME=$DOCKER_IMAGE_PUBLISH_NAME"; + + echo "[DEBUG] final: DOCKER_HUB_TAG=$DOCKER_HUB_TAG"; + + echo "[DEBUG] DOCKER_MULTI_ARCH_IMAGES=$DOCKER_MULTI_ARCH_IMAGES"; + + docker buildx imagetools create $DOCKER_MULTI_ARCH_IMAGES --tag $DOCKER_IMAGE_PUBLISH_REGISTRY/$DOCKER_IMAGE_PUBLISH_NAME:$DOCKER_HUB_TAG; + + if [ "${CI_COMMIT_TAG}" ]; then + + docker buildx imagetools create $DOCKER_MULTI_ARCH_IMAGES --tag $DOCKER_IMAGE_PUBLISH_REGISTRY/$DOCKER_IMAGE_PUBLISH_NAME:$CI_COMMIT_TAG; + + fi; + + after_script: + - docker logout docker.io + + environment: + name: DockerHub + url: $DOCKER_IMAGE_PUBLISH_URL + rules: + + - if: $CI_COMMIT_TAG + when: on_success + + - when: never + + + + + + + + + +Docker Container (dev): + extends: .build_docker_container + resource_group: docker-build + needs: + - Build Collection + rules: + - if: $CI_COMMIT_TAG + when: never + + - if: "$CI_COMMIT_AUTHOR =='nfc_bot '" + when: never + + - if: + $CI_COMMIT_BRANCH != "master" + && + $CI_PIPELINE_SOURCE == "push" + when: always + + - when: never + + +Docker Hub (dev): + extends: .publish-docker-hub + needs: + - "Docker Container (dev)" + resource_group: docker-build + rules: + - if: $CI_COMMIT_TAG + when: never + + - if: "$CI_COMMIT_AUTHOR =='nfc_bot '" + when: never + + - if: + $CI_COMMIT_BRANCH == "development" + && + $CI_PIPELINE_SOURCE == "push" + when: always + + - when: never + + +Docker Container: + extends: .build_docker_container + resource_group: docker-build + needs: + - Stage Collection + rules: + - if: $CI_COMMIT_TAG + when: on_success + + - if: "$CI_COMMIT_AUTHOR =='nfc_bot '" + when: never + + # - if: # Occur on merge + # $CI_COMMIT_BRANCH == "development" + # && + # $CI_PIPELINE_SOURCE == "push" + # when: always + + - when: never + + +Docker Hub: + extends: .publish-docker-hub + needs: + - "Docker Container" + - "Gitlab Release" + resource_group: docker-build + rules: + + - if: $CI_COMMIT_TAG + when: on_success + + - when: never \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..de6bfdd --- /dev/null +++ b/.gitmodules @@ -0,0 +1,8 @@ +[submodule "gitlab-ci"] + path = gitlab-ci + url = https://gitlab.com/nofusscomputing/projects/gitlab-ci.git + branch = development +[submodule "website-template"] + path = website-template + url = https://gitlab.com/nofusscomputing/infrastructure/website-template.git + branch = development diff --git a/.nfc_automation.yaml b/.nfc_automation.yaml new file mode 100644 index 0000000..077d7f4 --- /dev/null +++ b/.nfc_automation.yaml @@ -0,0 +1,8 @@ +--- + +role_git_conf: + gitlab: + submodule_branch: "development" + default_branch: development + mr_labels: ~"type::automation" ~"impact::0" ~"priority::0" + auto_merge: true diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..b60062e --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,12 @@ +{ + "recommendations": [ + "gitlab.gitlab-workflow", + "jebbs.markdown-extended", + "redhat.vscode-yaml", + "streetsidesoftware.code-spell-checker", + "streetsidesoftware.code-spell-checker-australian-english", + "redhat.ansible", + "ms-azuretools.vscode-docker", + "ms-python.python" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9183fba --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,18 @@ +{ + "gitlab.aiAssistedCodeSuggestions.enabled": false, + "gitlab.duoChat.enabled": false, + "cSpell.language": "en-AU,en", + "yaml.schemas": { + "https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/ansible.json#/$defs/tasks": [ + "file://${workspaceFolder}/playbooks/tasks/**", + "file://${workspaceFolder}/playbooks/tasks/**/**", + ], + "https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/inventory.json": [ + "file://${workspaceFolder}/includes/root/hosts.yaml" + ], + }, + "files.associations": { + "includes/etc/supervisor/supervisord.conf": "ini", + "includes/etc/supervisor/conf.d/*.conf": "ini", + } +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md index 701247b..59ec25a 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,18 @@ -
+ - -# No Fuss Computing - Ansible Collection Scan Agent - -
+# No Fuss Computing - Ansible Collection PHPIPAM Scan Agent ![Project Status - Active](https://img.shields.io/badge/Project%20Status-Active-green?logo=gitlab&style=plastic) +---- +
![Gitlab forks count](https://img.shields.io/badge/dynamic/json?label=Forks&query=%24.forks_count&url=https%3A%2F%2Fgitlab.com%2Fapi%2Fv4%2Fprojects%2F55052132%2F&color=ff782e&logo=gitlab&style=plastic) ![Gitlab stars](https://img.shields.io/badge/dynamic/json?label=Stars&query=%24.star_count&url=https%3A%2F%2Fgitlab.com%2Fapi%2Fv4%2Fprojects%2F55052132%2F&color=ff782e&logo=gitlab&style=plastic) [![Open Issues](https://img.shields.io/badge/dynamic/json?color=ff782e&logo=gitlab&style=plastic&label=Open%20Issues&query=%24.statistics.counts.opened&url=https%3A%2F%2Fgitlab.com%2Fapi%2Fv4%2Fprojects%2F55052132%2Fissues_statistics)](https://gitlab.com/nofusscomputing/projects/ansible/collections/phpipam_scan_agent/-/issues) +![GitHub forks](https://img.shields.io/github/forks/NofussComputing/ansible_collection_phpipam_scan_agent?logo=github&style=plastic&color=000000&labell=Forks) ![GitHub stars](https://img.shields.io/github/stars/NofussComputing/ansible_collection_phpipam_scan_agent?color=000000&logo=github&style=plastic) ![Github Watchers](https://img.shields.io/github/watchers/NofussComputing/ansible_collection_phpipam_scan_agent?color=000000&label=Watchers&logo=github&style=plastic) -![GitHub forks](https://img.shields.io/github/forks/NofussComputing/ansible_collection_scan_agent?logo=github&style=plastic&color=000000&labell=Forks) ![GitHub stars](https://img.shields.io/github/stars/NofussComputing/ansible_collection_scan_agent?color=000000&logo=github&style=plastic) ![Github Watchers](https://img.shields.io/github/watchers/NofussComputing/ansible_collection_scan_agent?color=000000&label=Watchers&logo=github&style=plastic) -
This project is hosted on [gitlab](https://gitlab.com/nofusscomputing/projects/ansible/collections/phpipam_scan_agent) and has a read-only copy hosted on [Github](https://github.com/NofussComputing/ansible_collection_phpipam_scan_agent). @@ -31,9 +29,22 @@ This project is hosted on [gitlab](https://gitlab.com/nofusscomputing/projects/a ![Gitlab build status - development](https://img.shields.io/badge/dynamic/json?color=ff782e&label=Build&query=0.status&url=https%3A%2F%2Fgitlab.com%2Fapi%2Fv4%2Fprojects%2F55052132%2Fpipelines%3Fref%3Ddevelopment&logo=gitlab&style=plastic) ![branch release version](https://img.shields.io/badge/dynamic/yaml?color=ff782e&logo=gitlab&style=plastic&label=Release&query=%24.commitizen.version&url=https%3A//gitlab.com/nofusscomputing/projects/ansible/collections/phpipam_scan_agent%2F-%2Fraw%2Fdevelopment%2F.cz.yaml) ---- -
-
+**Ansible Galaxy** + +[![Latest Version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgalaxy.ansible.com%2Fapi%2Fv3%2Fplugin%2Fansible%2Fcontent%2Fpublished%2Fcollections%2Findex%2Fnofusscomputing%2Fphp_scan_agent%2F&query=%24.highest_version.version&style=plastic&logo=ansible&logoColor=white&label=Latest%20Release&labelColor=black&color=cyan)](https://galaxy.ansible.com/ui/repo/published/nofusscomputing/ci_test_collection/) +![Downloads](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgalaxy.ansible.com%2Fapi%2Fv3%2Fplugin%2Fansible%2Fcontent%2Fpublished%2Fcollections%2Findex%2Fnofusscomputing%2Fphp_scan_agent%2F&query=%24.download_count&style=plastic&logo=ansible&logoColor=white&label=Downloads&labelColor=black&color=cyan) + +---- + +**Docker Hub** + +[![Docker Image Version](https://img.shields.io/docker/v/nofusscomputing/phpipam-scan-agent?sort=semver&style=plastic&logo=docker&logoColor=0db7ed&color=0db7ed&label=Latest%20Release)](https://hub.docker.com/r/nofusscomputing/phpipam-scan-agent) +[![Docker Pulls](https://img.shields.io/docker/pulls/nofusscomputing/phpipam-scan-agent?style=plastic&logo=docker&logoColor=0db7ed&color=0db7ed)](https://hub.docker.com/r/nofusscomputing/phpipam-scan-agent) + + + +---- links: diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..db63184 --- /dev/null +++ b/dockerfile @@ -0,0 +1,126 @@ + +FROM python:3.11-alpine3.19 as build + + +RUN apk update; \ + apk add gcc + + +RUN pip install --upgrade \ + setuptools \ + wheel + + +RUN apk add openjdk21-jdk; + + +RUN apk add \ + alpine-sdk \ + libffi-dev \ + maven; + + +ENV JAVA_HOME /usr/lib/jvm/java-21-openjdk + + +COPY requirements.txt /tmp/requirements.txt + + +RUN mkdir -p /tmp/python_modules; \ + cd /tmp/python_modules; \ + pip download --dest . --check-build-dependencies \ + supervisor==4.2.5 \ + -r /tmp/requirements.txt + +RUN mkdir -p /tmp/python_builds; + + +RUN cd /tmp/python_modules; \ + mkdir -p /tmp/python_builds; \ + echo "[DEBUG] PATH=$PATH"; \ + pip wheel --wheel-dir /tmp/python_builds --find-links . *.whl; \ + pip wheel --wheel-dir /tmp/python_builds --find-links . *.tar.gz; + + +RUN echo $(date)\ + cd /tmp; \ + ls -laR /tmp + + + + +FROM python:3.11-alpine3.19 + +LABEL \ + # org.opencontainers.image.authors="{contributor url}" \ + # org.opencontainers.image.url="{dockerhub url}" \ + # org.opencontainers.image.documentation="{docs url}" \ + # org.opencontainers.image.source="{repo url}" \ + # org.opencontainers.image.revision="{git commit sha at time of build}" \ + org.opencontainers.image.title="No Fuss Computings phpIPAM Scan Agent" \ + org.opencontainers.image.description="A phpIPAM Scan agent for local and remote networks" \ + org.opencontainers.image.vendor="No Fuss Computing" + # org.opencontainers.image.version="{git tag}" + +RUN apk --no-cache update; \ + apk add \ + openjdk21-jdk \ + git \ + dcron \ + nmap; + + +ENV JAVA_HOME /usr/lib/jvm/java-21-openjdk + + +COPY --from=build /tmp/python_builds /tmp/python_builds + + +RUN chmod 644 -R /etc/cron.d; \ + pip install /tmp/python_builds/*; \ + rm -R /tmp/python_builds + + +ARG COLLECTION_COMMIT=none + +ARG COLLECTION_BRANCH=development + +ARG COLLECTION_PACKAGE=dev + + +COPY includes/ / + +RUN mkdir -p /tmp/collection; \ + if [ "$COLLECTION_PACKAGE" != "dev" ]; then \ + echo "specified"; \ + ansible-galaxy collection install --force-with-deps --pre \ + $COLLECTION; \ + elif [ "$COLLECTION_PACKAGE" == "dev" ]; then \ + git clone \ + --depth=1 \ + -b $COLLECTION_BRANCH \ + https://gitlab.com/nofusscomputing/projects/ansible/collections/phpipam_scan_agent.git \ + /tmp/collection; \ + if [ "${COLLECTION_COMMIT}" != "none" ]; then git switch $COLLECTION_COMMIT; fi; \ + ansible-galaxy collection install --force-with-deps --pre \ + /tmp/collection/.; \ + rm -Rf /tmp/collection; \ + fi; \ + chmod +x /etc/cron.d/*; + + +WORKDIR /root + + +HEALTHCHECK --interval=10s --timeout=10s --start-period=5s --retries=3 CMD \ + supervisorctl status || exit 1 + + +ENV HTTP_PORT 5000 + +ENV ANSIBLE_FORCE_COLOR 'true' + +ENV ANSIBLE_LOG_PATH /var/log/ansible.log + + +CMD [ "/usr/local/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf" ] diff --git a/docs/articles/index.md b/docs/articles/index.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/contact.md b/docs/contact.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/operations/index.md b/docs/operations/index.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/projects/ansible/collection/index.md b/docs/projects/ansible/collection/index.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/projects/ansible/collection/phpipam_scan_agent/docker.md b/docs/projects/ansible/collection/phpipam_scan_agent/docker.md new file mode 100644 index 0000000..80e69c6 --- /dev/null +++ b/docs/projects/ansible/collection/phpipam_scan_agent/docker.md @@ -0,0 +1,55 @@ +--- +title: phpIPAM Scan Agent Docker Container +description: No Fuss Computings {php}IPAM Scan Agent Docker Container +date: 2024-02-21 +template: project.html +about: https://gitlab.com/nofusscomputing/projects/ansible/collections/phpipam_scan_agent +--- + +The Scan Agent Docker container has the Ansible collection installed which when launched will by default, start a Scan Server and scanner. The components running inside the container can be customised to suit different use cases. + +The Container has been setup to use supervisord, with the server and cron setup as the two available services. Due to the simplicity of supervisord, the container does have a healthcheck, that on failure means one or both of the services have failed. + + +## Usage + +Launching the docker container can be done with + +``` bash + +docker run \ + -d \ + -e "API_URL=" \ + -e "MYSQL_HOST=" \ + -e "MYSQL_USER=" \ + -e "MYSQL_PASSWORD=" \ + -e "SCANNER_TOKEN=" \ + -e "SCANNER_NAME=" \ + -e "SCANNER_CODE=" \ + -p "5000:5000" \ + --name scan-agent \ + scan-agent:latest; + +``` + +Variables must still be set for the running container, please review the [Scanner](scanner.md) or [Server](server.md) documentation as appropriate. + + +### Logs + +When viewing the container logs `docker logs `, what you will see is the Server component logs. This is by design. If however you are also running the scanner component, as is the default. To view those logs you will need to ensure that when launching the container that you specify environmental variable `ANSIBLE_LOG_PATH=/var/log/ansible.log`. This tells the scanner component to log to file at path `/var/log/ansible.log`. + +During the build of the container environmental variable `ANSIBLE_FORCE_COLOR='true'` is set, this enables the playbooks to be in colour when viewing the container logs. If this is not desired, set the variable to `ANSIBLE_FORCE_COLOR='false'` when launching the container. + + +### Volumes + +There are no volumes for this container. + +If you wish to customize the cronjob for the scan component within the container, mount a new cron file to path `/etc/cron.d/scanner`. The default cron file is as follows: + +``` yaml title="/etc/cron.d/scanner" linenums="1" + +--8<-- "includes/etc/cron.d/scanner" + +``` diff --git a/docs/projects/ansible/collection/phpipam_scan_agent/images/phpipam_api.png b/docs/projects/ansible/collection/phpipam_scan_agent/images/phpipam_api.png new file mode 100644 index 0000000..71c1a89 Binary files /dev/null and b/docs/projects/ansible/collection/phpipam_scan_agent/images/phpipam_api.png differ diff --git a/docs/projects/ansible/collection/phpipam_scan_agent/images/phpipam_scan_agent_details.png b/docs/projects/ansible/collection/phpipam_scan_agent/images/phpipam_scan_agent_details.png new file mode 100644 index 0000000..405581a Binary files /dev/null and b/docs/projects/ansible/collection/phpipam_scan_agent/images/phpipam_scan_agent_details.png differ diff --git a/docs/projects/ansible/collection/phpipam_scan_agent/index.md b/docs/projects/ansible/collection/phpipam_scan_agent/index.md new file mode 100644 index 0000000..35745c2 --- /dev/null +++ b/docs/projects/ansible/collection/phpipam_scan_agent/index.md @@ -0,0 +1,100 @@ +--- +title: phpIPAM Scan Agent +description: No Fuss Computings {php}IPAM Scan Agent for local and remote networks +date: 2024-02-20 +template: project.html +about: https://gitlab.com/nofusscomputing/projects/ansible/collections/phpipam_scan_agent +--- + + + +![Project Status - Active](https://img.shields.io/badge/Project%20Status-Active-green?logo=gitlab&style=plastic) + +[![Latest Version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgalaxy.ansible.com%2Fapi%2Fv3%2Fplugin%2Fansible%2Fcontent%2Fpublished%2Fcollections%2Findex%2Fnofusscomputing%2Fphp_scan_agent%2F&query=%24.highest_version.version&style=plastic&logo=ansible&logoColor=white&label=Latest%20Release&labelColor=black&color=cyan)](https://galaxy.ansible.com/ui/repo/published/nofusscomputing/ci_test_collection/) +![Downloads](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgalaxy.ansible.com%2Fapi%2Fv3%2Fplugin%2Fansible%2Fcontent%2Fpublished%2Fcollections%2Findex%2Fnofusscomputing%2Fphp_scan_agent%2F&query=%24.download_count&style=plastic&logo=ansible&logoColor=white&label=Downloads&labelColor=black&color=cyan) + +[![Docker Image Version](https://img.shields.io/docker/v/nofusscomputing/phpipam-scan-agent?sort=semver&style=plastic&logo=docker&logoColor=0db7ed&color=0db7ed&label=Latest%20Release)](https://hub.docker.com/r/nofusscomputing/phpipam-scan-agent) +[![Docker Pulls](https://img.shields.io/docker/pulls/nofusscomputing/phpipam-scan-agent?style=plastic&logo=docker&logoColor=0db7ed&color=0db7ed)](https://hub.docker.com/r/nofusscomputing/phpipam-scan-agent) + + + + +A phpIPAM scan agent designed for both local and remote network scanning. This Ansible Collection contains all of the componets required to launch a scan agent that will report back to the phpIPAM server. This collection is also built into it's own docker container and is [available on Docker Hub](https://hub.docker.com/r/nofusscomputing/phpipam-scan-agent). + +This collection has been broken down into two components, a [server](server.md) and a [scanner](scanner.md). The scanner as the name implies will scan the networks assigned to it by phpIPAM and on completing a scan of a subnet, will post the results to the Server component which will process the results, and update the phpIPAM MySQL/MariaDB database directly. + + +## Installation + +This collection is available on Ansible Galaxy and can be installed with `ansible-galaxy collection install nofusscomputing.phpipam_scan_agent`. When installing all of the required dependencies are installed. + +Prefer to use our [docker](docker.md) image? It's available on Docker Hub `docker pull nofusscomputing/phpipam-scan-agent:latest`. + + +## Features + +The following features are available or planned to be implmented: + +- Discover new hosts + +- [**ToDo** Execute scan from remote host](https://gitlab.com/nofusscomputing/projects/ansible/collections/phpipam_scan_agent/-/issues/7) + +- [**ToDo** Hosts check](https://gitlab.com/nofusscomputing/projects/ansible/collections/phpipam_scan_agent/-/issues/3) + +- [**ToDo** Host Self-Update](https://gitlab.com/nofusscomputing/projects/ansible/collections/phpipam_scan_agent/-/issues/2) + +- MAC Address updating* + + !!! info + It's only possible to obtain a MAC Address if the scanner is on the same L2 network (Broadcast Domain). Within the docs you will find the different methods available to achieve this. + +- [**ToDo** Remote Network Scanning](https://gitlab.com/nofusscomputing/projects/ansible/collections/phpipam_scan_agent/-/issues/1) + +- [**ToDo** Resolve DNS names](https://gitlab.com/nofusscomputing/projects/ansible/collections/phpipam_scan_agent/-/issues/4) + + +## phpIPAM Features + +This section describes the phpIPAM settings and what they each do for the scanner/server component. Regardless of any setting detailed below, unless otherwise specified. If the scan agent is not assigned to the subnet, the subnet will not be scanned. + + +### Discover new hosts + +- Location `Subnet -> Discover new hosts` + +When this setting is enabled, the scanner will scan the entire subnet that has been assigned to it. Every host that is found is updated in phpIPAM. + +!!! note + the work in [Hosts check](https://gitlab.com/nofusscomputing/projects/ansible/collections/phpipam_scan_agent/-/issues/3), will adjust this behaviour to only add hosts that dont exist. + + +## Development Notes + +Contributions to this project are welcome. Below you will find some useful commands for use during development. + +``` bash +# To build the container. ensure the changes are commited and push to you feature branch +docker build . --tag scan-agent:dev --build-arg COLLECTION_BRANCH= --build-arg COLLECTION_COMMIT=$(git log -n1 --format=format:"%H") + + +# Launch your build container +docker run \ + -d \ + -e "API_URL=" \ + -e "MYSQL_HOST=" \ + -e "MYSQL_USER=" \ + -e "MYSQL_PASSWORD=" \ + -e "SCANNER_TOKEN=" \ + -e "SCANNER_NAME=" \ + -e "SCANNER_CODE=" \ + -e "ANSIBLE_LOG_PATH=/var/log/ansible.log" \ + -p "5000:5000" \ + --name scan-agent \ + scan-agent:dev; + +# remove launched dev container +docker rm --force scan-agent + +``` + +Our docker build file has been designed so that during development it will pull from the repository branch as specified to find the collection to install. if you fail to specify your feature branch, the collection will not install the work you have been doing. diff --git a/docs/projects/ansible/collection/phpipam_scan_agent/scanner.md b/docs/projects/ansible/collection/phpipam_scan_agent/scanner.md new file mode 100644 index 0000000..bddabe2 --- /dev/null +++ b/docs/projects/ansible/collection/phpipam_scan_agent/scanner.md @@ -0,0 +1,101 @@ +--- +title: phpIPAM Scanner +description: No Fuss Computings {php}IPAM Scan Agent scenner component +date: 2024-02-20 +template: project.html +about: https://gitlab.com/nofusscomputing/projects/ansible/collections/phpipam_scan_agent +--- + +The Scan Agent Scanner component is intended to scan networks that are assigned to it by the phpIPAM server. It can be installed and ran from any host that is capable of running python. + + +## Usage + +After installing the collection, running the agent is as simple as running the following command: + +``` bash + +ansible-playbook nofusscomputing.phpipam_scan_agent.agent \ + --extra-vars "api_url=" \ + --extra-vars "client_token=" \ + --extra-vars "client_name=" \ + --extra-vars "scanagent_code=" + +``` + +_See below for the variable details_ + + +### phpIPAM API + +The scanner component requires API access to phpIPAM. THe API user that is used, must only be given read only access to the API. + + +### Variables + +The variables described below, if optional the value specified here is the default value. Any variable that can be set via environmental variables have the variable name enclosed in `[]` + +``` yaml + +client_token: "" # Mandatory, String client api token to connect to phpIPAM API [SCANNER_TOKEN] +client_name: "" # Mandatory, String. The scanner name as set in phpIPAM interface [SCANNER_NAME] +scanagent_code: "" # Mandatory, String. Scan Agent Code as set in phpIPAM interface [SCANNER_CODE] + + +nfc_c_http_port: 5000 # Optional, Integer. http port to connect to the server. [HTTP_PORT] +nfc_c_http_server: http://127.0.0.1 # Optional, Integer. url with protocol of the Scan Server to connect to. [HTTP_URL] + + +api_url: http://127.0.0.1 # Optional, String. url with protocol of the phpIPAM API to connect to. [API_URL] + + +nfc_c_cache_expire_time: 1800 # Optional, Integer. Time in seconds to expire the phpIPAM cache. +nfc_c_epoch_time_offset: 0 # optional, int. Value in seconds to offset the time + +``` + +!!! tip + You can specify environmental variable `ANSIBLE_LOG_PATH=/var/log/ansible.log`, which will tell the scanner component to log to a file at path `/var/log/ansible.log` + + +#### phpIPAM Interface variable Mapping + +These images are of the phpIPAM interface that show in green text the variable name that would be set as detailed above. + +![phpIPAM API](images/phpipam_api.png) + +phpIPAM API Settings + +---- + +![phpIPAM Scan Agent](images/phpipam_scan_agent_details.png) + +phpIPAM Scan Agent Settings + + +## Workflow + +The scanner component has the following workflow: + +1. Expire cache, if cache expiry has elapsed. + +1. Fetch from the phpIPAM API, the subnets assigned to it. _results are cached_ + +1. Fetch ALL address' from phpIPAM API, that are assosiated with agent subnets. _results are cached_ + +1. For each network: + + 1. conduct Scan of network. + + !!! info + The following details are included in the scan report: + + - IP Address + + - MAC Address* _Only if the scanner is on the same L2 network (Broadcast Domain)_ + + 1. Re-format nmap scan report to format Server component recognizes. + + 1. upload scan report to configured Server. + +1. workflow complete. diff --git a/docs/projects/ansible/collection/phpipam_scan_agent/server.md b/docs/projects/ansible/collection/phpipam_scan_agent/server.md new file mode 100644 index 0000000..06f103e --- /dev/null +++ b/docs/projects/ansible/collection/phpipam_scan_agent/server.md @@ -0,0 +1,56 @@ +--- +title: phpIPAM Scan Server +description: No Fuss Computings {php}IPAM Scan Agent Server component +date: 2024-02-20 +template: project.html +about: https://gitlab.com/nofusscomputing/projects/ansible/collections/phpipam_scan_agent +--- + +The Scan Agent Server component is intended to act as the go between for the Scanner component and phpIPAM. + + +## Usage + +After installing the collection, running the server is as simple as running the following command: + +``` bash + +ansible-rulebook -r nofusscomputing.phpipam_scan_agent.agent_receive + +``` + + +### Variables + +The variables described below, if optional the value specified here is the default value. All variables that are used by the server component are environmental variables that must be set before execution. + +``` bash + +# phpIPAM MariaDB/MySQL Variables +MYSQL_HOST= # Mandatory, String. IP/DNS of host to connect. +MYSQL_PORT=3306 # Optional, Integer. port to use for connection. +MYSQL_USER= # Mandatory, String. User to authenticate with. +MYSQL_PASSWORD= # Mandatory, String. Password for the user to connect with. + + +# Server Component Variables +HTTP_PORT=5000 # Optional, Integer. The port for the Server component to listen for connections. + +``` + + +# Workflow + +The Server componet has the following workflow: + +1. Receive the Scanner component report. + +1. check if the Scanner Code is in the DB + + - _if no results found, no further processing occurs_ + +1. Confirm the subnet scanned is assigned to the scanner + + - _if no results found, no further processing occurs_ + +1. Update the phpIPAM MariaDB/MySQL database directly diff --git a/docs/projects/ansible/index.md b/docs/projects/ansible/index.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/projects/index.md b/docs/projects/index.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/tags.md b/docs/tags.md new file mode 100644 index 0000000..e69de29 diff --git a/extensions/eda/rulebooks/agent_receive.yml b/extensions/eda/rulebooks/agent_receive.yml new file mode 100644 index 0000000..5a64137 --- /dev/null +++ b/extensions/eda/rulebooks/agent_receive.yml @@ -0,0 +1,25 @@ +- name: Agent Webhook + hosts: all + execution_strategy: parallel + + sources: + - name: Webhook + ansible.eda.webhook: + host: 0.0.0.0 + port: "{{ HTTP_PORT }}" + + rules: + + - name: Process inbound Subnet Scans + condition: true # Always run action + actions: + + + - print_event: + pretty: true + + + - run_playbook: + name: nofusscomputing.phpipam_scan_agent.server + extra_vars: + inbound_data: "{{ event.payload }}" diff --git a/galaxy.yml b/galaxy.yml new file mode 100644 index 0000000..5247060 --- /dev/null +++ b/galaxy.yml @@ -0,0 +1,87 @@ +### REQUIRED +# The namespace of the collection. This can be a company/brand/organization or product namespace under which all +# content lives. May only contain alphanumeric lowercase characters and underscores. Namespaces cannot start with +# underscores or numbers and cannot contain consecutive underscores +namespace: nofusscomputing + +# The name of the collection. Has the same character restrictions as 'namespace' +name: phpipam_scan_agent + +# The version of the collection. Must be compatible with semantic versioning +version: 0.0.1 + +# The path to the Markdown (.md) readme file. This path is relative to the root of the collection +readme: README.md + +# A list of the collection's content authors. Can be just the name or in the format 'Full Name (url) +# @nicks:irc/im.site#channel' +authors: + - No Fuss Computing + + +### OPTIONAL but strongly recommended +# A short summary description of the collection +description: A phpIPAM Scan Agent that can be used in local or remote sites. + +# Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only +# accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file' +license: + - MIT + +# The path to the license file for the collection. This path is relative to the root of the collection. This key is +# mutually exclusive with 'license' +license_file: '' + +# A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character +# requirements as 'namespace' and 'name' +tags: + - ipam + - scan_agent + - network + - phpipam + - tools + +# Collections that this collection requires to be installed for it to be usable. The key of the dict is the +# collection label 'namespace.name'. The value is a version range +# L(specifiers,https://python-semanticversion.readthedocs.io/en/latest/#requirement-specification). Multiple version +# range specifiers can be set and are separated by ',' +dependencies: + ansible.eda: '1.4.5' + community.mysql: '3.8.0' + ansible.utils: '2.12.0' + +# The URL of the originating SCM repository +repository: https://gitlab.com/nofusscomputing/projects/ansible/collections/phpipam_scan_agent + +# The URL to any online docs +documentation: https://nofusscomputing.com/projects/ansible/collection/phpipam_scan_agent/ + +# The URL to the homepage of the collection/project +# homepage: https://example.com + +# The URL to the collection issue tracker +issues: https://gitlab.com/nofusscomputing/projects/ansible/collections/phpipam_scan_agent/-/issues + +# A list of file glob-like patterns used to filter any files or directories that should not be included in the build +# artifact. A pattern is matched from the relative path of the file or directory of the collection directory. This +# uses 'fnmatch' to match the files or directories. Some directories and files like 'galaxy.yml', '*.pyc', '*.retry', +# and '.git' are always filtered. Mutually exclusive with 'manifest' +build_ignore: + - .vscode/* + - artifacts/* + - docs/* + - .gitlab* + - includes/ + - website-template/* + - .ansible-lint-ignore + - .cz.yaml + - .nfc_automation.yaml + - dockerfile + - mkdocs.yaml + +# A dict controlling use of manifest directives used in building the collection artifact. The key 'directives' is a +# list of MANIFEST.in style +# L(directives,https://packaging.python.org/en/latest/guides/using-manifest-in/#manifest-in-commands). The key +# 'omit_default_directives' is a boolean that controls whether the default directives are used. Mutually exclusive +# with 'build_ignore' +# manifest: null diff --git a/gitlab-ci b/gitlab-ci new file mode 160000 index 0000000..d29064f --- /dev/null +++ b/gitlab-ci @@ -0,0 +1 @@ +Subproject commit d29064f1490073599518b629c7bf6585b48c8736 diff --git a/includes/etc/cron.d/scanner b/includes/etc/cron.d/scanner new file mode 100644 index 0000000..acdab00 --- /dev/null +++ b/includes/etc/cron.d/scanner @@ -0,0 +1,5 @@ +# +# Default Scanner Scheduled Job +# + +*/5 * * * * ansible-playbook nofusscomputing.phpipam_scan_agent.agent -v diff --git a/includes/etc/supervisor/conf.d/cron.conf b/includes/etc/supervisor/conf.d/cron.conf new file mode 100644 index 0000000..d677dd1 --- /dev/null +++ b/includes/etc/supervisor/conf.d/cron.conf @@ -0,0 +1,8 @@ +[program:cron] +startsecs=0 +stopwaitsecs=55 +autostart=true +autorestart=true +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes=0 +command=/usr/sbin/crond -f -L /var/log/crond.log -l info diff --git a/includes/etc/supervisor/conf.d/rulebook.conf b/includes/etc/supervisor/conf.d/rulebook.conf new file mode 100644 index 0000000..c63aed3 --- /dev/null +++ b/includes/etc/supervisor/conf.d/rulebook.conf @@ -0,0 +1,8 @@ +[program:rulebook] +startsecs=0 +stopwaitsecs=55 +command=ansible-rulebook -r nofusscomputing.phpipam_scan_agent.agent_receive --env-vars "HTTP_PORT" -i /root/hosts.yaml -v +autorestart=true +autostart=true +stdout_logfile=/dev/fd/1 +stdout_logfile_maxbytes=0 diff --git a/includes/etc/supervisor/supervisord.conf b/includes/etc/supervisor/supervisord.conf new file mode 100644 index 0000000..a19d9be --- /dev/null +++ b/includes/etc/supervisor/supervisord.conf @@ -0,0 +1,27 @@ + +[unix_http_server] +file=/var/run/supervisor.sock +chmod=0700 + +;[inet_http_server] +;port = :9001 +; username = user +; password = 123 + +[supervisord] +logfile=/var/log/supervisord.log +pidfile=/var/run/supervisord.pid +nodaemon = true +user=root + + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + + +[supervisorctl] +serverurl=unix:///var/run/supervisor.sock + + +[include] +files = /etc/supervisor/conf.d/*.conf diff --git a/includes/root/hosts.yaml b/includes/root/hosts.yaml new file mode 100644 index 0000000..c6b01de --- /dev/null +++ b/includes/root/hosts.yaml @@ -0,0 +1,6 @@ +all: + hosts: + localhost: + vars: + ansible_connection: local + diff --git a/meta/runtime.yml b/meta/runtime.yml new file mode 100644 index 0000000..2fe301c --- /dev/null +++ b/meta/runtime.yml @@ -0,0 +1,52 @@ +--- +# Collections must specify a minimum required ansible version to upload +# to galaxy +requires_ansible: '>=2.9.10' + +# Content that Ansible needs to load from another location or that has +# been deprecated/removed +# plugin_routing: +# action: +# redirected_plugin_name: +# redirect: ns.col.new_location +# deprecated_plugin_name: +# deprecation: +# removal_version: "4.0.0" +# warning_text: | +# See the porting guide on how to update your playbook to +# use ns.col.another_plugin instead. +# removed_plugin_name: +# tombstone: +# removal_version: "2.0.0" +# warning_text: | +# See the porting guide on how to update your playbook to +# use ns.col.another_plugin instead. +# become: +# cache: +# callback: +# cliconf: +# connection: +# doc_fragments: +# filter: +# httpapi: +# inventory: +# lookup: +# module_utils: +# modules: +# netconf: +# shell: +# strategy: +# terminal: +# test: +# vars: + +# Python import statements that Ansible needs to load from another location +# import_redirection: +# ansible_collections.ns.col.plugins.module_utils.old_location: +# redirect: ansible_collections.ns.col.plugins.module_utils.new_location + +# Groups of actions/modules that take a common set of options +# action_groups: +# group_name: +# - module1 +# - module2 diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..d390a4b --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,44 @@ +INHERIT: website-template/mkdocs.yml + +docs_dir: 'docs' + +repo_name: phpIPAM Scan Agent +repo_url: https://gitlab.com/nofusscomputing/projects/ansible/collections/phpipam_scan_agent +edit_uri: '/-/ide/project/nofusscomputing/projects/ansible/collections/phpipam_scan_agent/edit/development/-/docs/' + +nav: + +- Home: index.md + +- Articles: + + - articles/index.md + +- Projects: + + - projects/index.md + + - Ansible: + + - projects/ansible/index.md + + - Collections: + + - projects/ansible/collection/index.md + + - phpIPAM Scan Agent: + + - projects/ansible/collection/phpipam_scan_agent/index.md + + - Docker Container: projects/ansible/collection/phpipam_scan_agent/docker.md + + - Scanner: projects/ansible/collection/phpipam_scan_agent/scanner.md + + - Server: projects/ansible/collection/phpipam_scan_agent/server.md + +- Operations: + + - operations/index.md + +- Contact Us: contact.md + diff --git a/playbooks/agent.yaml b/playbooks/agent.yaml new file mode 100644 index 0000000..1307424 --- /dev/null +++ b/playbooks/agent.yaml @@ -0,0 +1,100 @@ +- name: Fetch API Data + hosts: localhost + become: false + gather_facts: false + + tasks: + + + - name: Fetch Required Environmental Variables + ansible.builtin.set_fact: + client_token: "{{ lookup('ansible.builtin.env', 'SCANNER_TOKEN') | default('') }}" + client_name: "{{ lookup('ansible.builtin.env', 'SCANNER_NAME') }}" + scanagent_code: "{{ lookup('ansible.builtin.env', 'SCANNER_CODE') | default('') }}" + api_url: "{{ lookup('ansible.builtin.env', 'API_URL') | default('') }}" + no_log: true + when: > + client_token is not defined + and + client_name is not defined + and + scanagent_code is not defined + and + api_url is not defined + + + - name: Fetch Required Environmental Variable - HTTP_URL + ansible.builtin.set_fact: + nfc_c_http_server: "{{ lookup('ansible.builtin.env', 'HTTP_URL') | default('') }}" + when: > + lookup('ansible.builtin.env', 'HTTP_URL') | default('') != '' + + + - name: Fetch Required Environmental Variable - HTTP_PORT + ansible.builtin.set_fact: + nfc_c_http_port: "{{ lookup('ansible.builtin.env', 'HTTP_PORT') | default('') }}" + when: > + lookup('ansible.builtin.env', 'HTTP_PORT') | default('') != '' + + + - name: Confirm 'api_url' is Set + ansible.builtin.assert: + that: + - api_url is defined + - api_url != '' + msg: "missing Required Variables" + + + - name: Confirm 'client_token' is Set + ansible.builtin.assert: + that: + - client_token is defined + msg: "missing Required Variables" + + + - name: Confirm 'client_name' is Set + ansible.builtin.assert: + that: + - client_name is defined + msg: "missing Required Variables" + + + - name: Confirm 'scanagent_code' is Set + ansible.builtin.assert: + that: + - scanagent_code is defined + msg: "missing Required Variables" + + + - name: Create API Cache Directory + ansible.builtin.file: + path: "{{ nfc_c_path_cache }}" + state: directory + + - name: Agent ID + ansible.builtin.include_tasks: + file: tasks/agent_id.yaml + + + - name: Subnets + ansible.builtin.include_tasks: + file: tasks/subnets.yaml + + + - name: Scan Subnet + ansible.builtin.include_tasks: + file: tasks/scan_subnet.yaml + loop: "{{ nfc_c_scan_agent_subnets }}" + loop_control: + loop_var: subnet + + + vars: # ToDo: remove the below t4est vars + nfc_c_http_port: 5000 + nfc_c_http_server: http://127.0.0.1 + + api_address: addresses + api_subnets: subnets + api_scanagents: tools/scanagents + nfc_c_path_cache: "{{ playbook_dir }}/../cache" + nfc_c_cache_expire_time: 1800 diff --git a/playbooks/filter_plugins/phpipam_db_ip.py b/playbooks/filter_plugins/phpipam_db_ip.py new file mode 100644 index 0000000..46479e0 --- /dev/null +++ b/playbooks/filter_plugins/phpipam_db_ip.py @@ -0,0 +1,57 @@ + +def ip2ipam(a): + """convert an IPv4 IP Address to phpIPAM decimal IP Address""" + + s_bin = '' + + try: + + if '.' in str(a): + + a = a.split('.') + + for octet in a: + + s_bin = s_bin + '{0:08b}'.format(int(octet)) + + except Exception as e: + + raise AnsibleFilterError("to_nice_yaml - %s" % to_native(e), orig_exc=e) + + + return int(s_bin, 2) + + +def ipam2ip(a): + """convert a phpIPAM decimal IP Address to an IPv4 IP Address""" + + s_bin = '' + + try: + + s_bin = '{0:08b}'.format(int(a)) + + s_bin = str( + str(int(s_bin[0:8], 2)) + '.' + + str(int(s_bin[8:16], 2)) + '.' + + str(int(s_bin[16:24], 2)) + '.' + + str(int(s_bin[24:32], 2)) + ) + + except Exception as e: + + raise AnsibleFilterError("to_nice_yaml - %s" % to_native(e), orig_exc=e) + + return s_bin + + +class FilterModule(object): + """my format filters.""" + + + def filters(self): + """Return the filter list.""" + return { + 'ip2ipam': ip2ipam, + 'ipam2ip': ipam2ip + } diff --git a/playbooks/server.yaml b/playbooks/server.yaml new file mode 100644 index 0000000..dc41f9b --- /dev/null +++ b/playbooks/server.yaml @@ -0,0 +1,72 @@ +--- +- name: Agent Server + hosts: all + gather_facts: false + + + tasks: + + + - name: Fetch Required Environmental Variables + ansible.builtin.set_fact: + nfc_c_mysql_host: "{{ lookup('ansible.builtin.env', 'MYSQL_HOST') | default('') }}" + nfc_c_mysql_port: "{{ lookup('ansible.builtin.env', 'MYSQL_PORT') | default(3306) | int }}" + nfc_c_mysql_user: "{{ lookup('ansible.builtin.env', 'MYSQL_USER') | default('') }}" + nfc_c_mysql_password: "{{ lookup('ansible.builtin.env', 'MYSQL_PASSWORD') | default('') }}" + no_log: true + + + - name: TRACE Inbound data Received + ansible.builtin.debug: + msg: "{{ inbound_data }}" + + + - name: Fetch Agent Details + community.mysql.mysql_query: + login_host: "{{ nfc_c_mysql_host }}" + login_port: "{{ nfc_c_mysql_port | default(3306) | int }}" + login_user: "{{ nfc_c_mysql_user }}" + login_password: "{{ nfc_c_mysql_password }}" + + login_db: 'phpipam' + query: > + SELECT id, code FROM scanAgents WHERE code='{{ inbound_data.code }}' + single_transaction: true + register: mysql_query_agent_details + + + - name: Confirm Subnet Assignment + community.mysql.mysql_query: + login_host: "{{ nfc_c_mysql_host }}" + login_port: "{{ nfc_c_mysql_port | default(3306) | int }}" + login_user: "{{ nfc_c_mysql_user }}" + login_password: "{{ nfc_c_mysql_password }}" + + login_db: 'phpipam' + query: > + SELECT id, subnet FROM subnets WHERE + scanAgent='{{ mysql_query_agent_details.query_result[0][0].id }}' + and + subnet='{{ (inbound_data.scan.subnet | split('/'))[0] | ip2ipam }}' + and + mask = '{{ (inbound_data.scan.subnet | split('/'))[1] | int }}' + single_transaction: true + register: mysql_query_agent_subnets + + + - name: Arrange Subnets + ansible.builtin.set_fact: + agent_subnets: "{{ agent_subnets | default([]) + [ item.id ] }}" + + loop: "{{ mysql_query_agent_subnets.query_result[0] }}" + + + - name: Process Scan results + ansible.builtin.include_tasks: + file: tasks/server/subnet_scan.yaml + vars: + scan_result: "{{ inbound_data.scan.results }}" + + + vars: + ansible_connection: local diff --git a/playbooks/tasks/agent_id.yaml b/playbooks/tasks/agent_id.yaml new file mode 100644 index 0000000..2bd20d3 --- /dev/null +++ b/playbooks/tasks/agent_id.yaml @@ -0,0 +1,18 @@ +--- + +- name: Get My ScanAgent ID + ansible.builtin.include_tasks: + file: tasks/api_call.yaml + vars: + api_client_name: "{{ client_name }}" + api_token: "{{ client_token }}" + api_path: "{{ api_scanagents }}" + api_query_string: "filter_by=code&filter_value={{ scanagent_code }}" + + +- name: My ScanAgent ID + ansible.builtin.set_fact: + nfc_c_scan_agent_id: "{{ data[0].id }}" + failed_when: data[0].id is not defined + vars: + data: "{{ lookup('file', cache_filepath) }}" diff --git a/playbooks/tasks/api_call.yaml b/playbooks/tasks/api_call.yaml new file mode 100644 index 0000000..99e5cac --- /dev/null +++ b/playbooks/tasks/api_call.yaml @@ -0,0 +1,112 @@ +--- +- name: Try/Catch + block: + + + - name: Mandatory Variables set + ansible.builtin.assert: + that: + - api_client_name is defined + - api_path is defined + - api_token is defined + - api_url is defined + + + - name: API Facts + ansible.builtin.set_fact: + epoch: "{{ ((('%Y-%m-%d %H:%M:%S' | strftime) | string | to_datetime) - ('1970-01-01 00:00:00' | to_datetime)).total_seconds() | int }}" + expired: false + cache_filepath: >- + {{ nfc_c_path_cache }}/{{ api_path }} + {%- if api_query_string is defined -%} + _{{ ((api_query_string | split('&'))[0] | split('='))[1] | lower }}_{{ ((api_query_string | split('&'))[1] | split('='))[1] | lower }} + {%- endif -%}.json + + + - name: check Cache Files + ansible.builtin.stat: + path: "{{ cache_filepath }}" + register: cache_files + + + - name: Expire + ansible.builtin.set_fact: + expired: "{{ ((epoch | int + (nfc_c_epoch_time_offset | default(0)) | int) >= ((cache_files.stat.mtime | int) + nfc_c_cache_expire_time | int) | int ) | bool }}" + when: cache_files.stat.exists + + + - name: TRACE - Cached file + ansible.builtin.debug: + msg: + - "exists: {{ cache_files.stat.exists | default('') }}" + - "mtime: {{ cache_files.stat.mtime | default(0) | int }}" + - "expire: {{ (cache_files.stat.mtime | int) + nfc_c_cache_expire_time | int }}" + - "epoch: {{ (epoch | int + (nfc_c_epoch_time_offset | default(0)) | int) | int }} [{{ nfc_c_cache_expire_time }}]" + - "epoch: {{ epoch }}" + - "expired: {{ expired }}" + when: cache_files.stat.exists + + - name: Expire Cache + ansible.builtin.file: + path: "{{ cache_files.stat.path }}" + state: absent + when: > + expired + and + cache_files.stat.exists + + + - name: > + PHPIPAM API Call - {{ api_path }}{%- if api_query_string is defined -%} + /?{{ api_query_string }} + {%- endif %} + ansible.builtin.uri: + url: >- + {{ api_url }}/api/{{ api_client_name }}/{{ api_path }} + {%- if api_query_string is defined -%} + /?{{ api_query_string }} + {%- endif %} + headers: + token: "{{ api_token }}" + return_content: true + status_code: + - 200 + - 404 + validate_certs: false + changed_when: api_call.json | length | int > 0 + no_log: true + register: api_call + when: > + ( + expired + and + cache_files.stat.exists + ) + or + not cache_files.stat.exists + + + - name: Create Cache DIR + ansible.builtin.file: + path: "{{ nfc_c_path_cache }}/{{ (api_path | split('/'))[0] }}" + state: directory + when: > + '/' in api_path + and + api_call.status | default(0) | int != 404 + + + - name: Cache Data + ansible.builtin.copy: + content: "{{ api_call.json.data | to_nice_json }}" + dest: "{{ cache_filepath }}" + when: > + ( + expired + and + cache_files.stat.exists + ) + or + not cache_files.stat.exists + and + api_call.status | default(0) | int != 404 diff --git a/playbooks/tasks/scan_subnet.yaml b/playbooks/tasks/scan_subnet.yaml new file mode 100644 index 0000000..5847c43 --- /dev/null +++ b/playbooks/tasks/scan_subnet.yaml @@ -0,0 +1,70 @@ +--- + +- name: Scan subnet + ansible.builtin.command: + cmd: nmap -sn "{{ subnet.address }}" -oX - + become: true + register: nmap_scan +- name: Get subnets Address' + ansible.builtin.include_tasks: + file: tasks/api_call.yaml + vars: + api_client_name: "{{ client_name }}" + api_token: "{{ client_token }}" + api_path: "{{ api_address }}" + api_query_string: "filter_by=subnetId&filter_value={{ subnet.id }}" + + +- name: Load Subnet + ansible.builtin.set_fact: + cached_subnet: "{{ lookup('file', cache_filepath) }}" + cacheable: false + no_log: true + when: > + api_call.status | default(0) | int != 404 + + +- name: Process Scan Results + ansible.builtin.set_fact: + subnet_scan_results: |- + [ + {% for scanned_host in ((nmap_scan.stdout | ansible.utils.from_xml) | from_yaml).nmaprun.host | default([]) %} + {% if + scanned_host.address[0]['@addrtype'] | default('') == 'ipv4' + or + scanned_host.address['@addrtype'] | default('') == 'ipv4' + %} + { + {% for cached_host in cached_subnet | default([]) -%} + {%- if cached_host.ip == scanned_host.address['@addr'] | default(scanned_host.address[0]['@addr']) -%} + "id": {{ cached_host.id }}, + {%- endif -%} + {%- endfor %} + "subnetId": "{{ subnet.id }}", + "ip": "{{ scanned_host.address['@addr'] | default(scanned_host.address[0]['@addr']) }}", + "lastSeen": "{{ nmap_scan.start }}", + {% if scanned_host.address['@addrtype'] | default(scanned_host.address[1]['@addrtype']) == 'mac' %} + "mac": "{{ scanned_host.address['@addr'] | default(scanned_host.address[1]['@addr']) | upper }}" + {% endif %} + }, + {% endif %} + {% endfor %} + ] + +- name: To JSON + ansible.builtin.set_fact: + subnet_scan_results: "{{ subnet_scan_results | from_yaml }}" + + +- name: Upload Scan Results + ansible.builtin.uri: + url: "{{ nfc_c_http_server }}:{{ nfc_c_http_port }}/" + method: POST + body_format: json + body: { + "code": "{{ scanagent_code }}", + "scan": { + "subnet": "{{ subnet.address }}", + "results": "{{ subnet_scan_results }}" + } + } diff --git a/playbooks/tasks/server/ipaddress.yaml b/playbooks/tasks/server/ipaddress.yaml new file mode 100644 index 0000000..52ceb04 --- /dev/null +++ b/playbooks/tasks/server/ipaddress.yaml @@ -0,0 +1,63 @@ +--- + + +- name: Update IP Address' found + community.mysql.mysql_query: + login_host: "{{ nfc_c_mysql_host }}" + login_port: "{{ nfc_c_mysql_port | default(3306) | int }}" + login_user: "{{ nfc_c_mysql_user }}" + login_password: "{{ nfc_c_mysql_password }}" + + login_db: 'phpipam' + query: |- + {% if + scan_address.rowcount[0] | int > 0 + %} + + UPDATE ipaddresses + SET + lastSeen = '{{ scan_address.ipaddress.lastSeen }}' + + {% if scan_address.ipaddress.mac | default('') != '' %}, + + mac = '{{ scan_address.ipaddress.mac }}' + + {% endif %} + + WHERE + id = {{ scan_address.query_result[0][0].id | int }} + + {% elif + scan_address.rowcount[0] | int == 0 + %} + + INSERT INTO ipaddresses + ( + subnetId, + ip_addr, + description, + {% if scan_address.ipaddress.mac | default('') != '' %}mac,{% endif %} + note, + lastSeen + ) + VALUES + ( + {{ scan_address.ipaddress.subnetId | int }}, + '{{ scan_address.ipaddress.ip | ip2ipam }}', + '-- autodiscovered --', + + {% if scan_address.ipaddress.mac | default('') != '' %} + + '{{ scan_address.ipaddress.mac }}', + + {% endif %} + + 'This host was autodiscovered on {{ scan_address.ipaddress.lastSeen }}', + '{{ scan_address.ipaddress.lastSeen }}' + ) + + {% endif %} + + single_transaction: true + when: > + scan_address.ipaddress.subnetId | int in agent_subnets diff --git a/playbooks/tasks/server/subnet_scan.yaml b/playbooks/tasks/server/subnet_scan.yaml new file mode 100644 index 0000000..d9eb038 --- /dev/null +++ b/playbooks/tasks/server/subnet_scan.yaml @@ -0,0 +1,35 @@ +--- + + +- name: Match Scan Addresses to DB Details + community.mysql.mysql_query: + login_host: "{{ nfc_c_mysql_host }}" + login_port: "{{ nfc_c_mysql_port | default(3306) | int }}" + login_user: "{{ nfc_c_mysql_user }}" + login_password: "{{ nfc_c_mysql_password }}" + + login_db: 'phpipam' + query: |- + SELECT + id, description, state, note, lastSeen + FROM ipaddresses + WHERE + {% if ipaddress.id is defined %} + id='{{ ipaddress.id }}' + AND + {% endif %} + ip_addr='{{ ipaddress.ip | ip2ipam }}' + single_transaction: true + register: mysql_query_find_ipaddress + loop: "{{ scan_result }}" + loop_control: + loop_var: ipaddress + + +- name: Update IP Addresses + ansible.builtin.include_tasks: + file: tasks/server/ipaddress.yaml + loop: "{{ mysql_query_find_ipaddress.results | default([]) }}" + loop_control: + loop_var: scan_address + label: "{{ scan_address }}" diff --git a/playbooks/tasks/subnets.yaml b/playbooks/tasks/subnets.yaml new file mode 100644 index 0000000..b907daf --- /dev/null +++ b/playbooks/tasks/subnets.yaml @@ -0,0 +1,29 @@ +--- + +- name: Reset Subnet List + ansible.builtin.set_fact: + nfc_c_scan_agent_subnets: [] + + +- name: Get subnets to Scan + ansible.builtin.include_tasks: + file: tasks/api_call.yaml + vars: + api_client_name: "{{ client_name }}" + api_token: "{{ client_token }}" + api_path: "{{ api_subnets }}" + api_query_string: "filter_by=scanAgent&filter_value={{ nfc_c_scan_agent_id }}" + + +- name: Update Subnets List + ansible.builtin.set_fact: + nfc_c_scan_agent_subnets: "{{ nfc_c_scan_agent_subnets + [{ + 'id': network.id, + 'address': network.subnet + '/' + network.mask + }] }}" + loop: "{{ data | default ([]) }}" + loop_control: + loop_var: network + vars: + data: "{{ lookup('file', cache_filepath) }}" + when: network.discoverSubnet | int == 1 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4851b8d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +ansible-core==2.16.1 +ansible-rulebook==1.0.5 +PyMySQL==1.1.0 +xmltodict==0.13.0 diff --git a/website-template b/website-template new file mode 160000 index 0000000..f5a82d3 --- /dev/null +++ b/website-template @@ -0,0 +1 @@ +Subproject commit f5a82d3604faca56756eec91acee28ff89defd1d