Code development platform for open source projects from the European Union institutions

Skip to content
Snippets Groups Projects
Commit 5f1f239f authored by SILVA Ricardo's avatar SILVA Ricardo
Browse files

Vault public git migration

parent 680c133a
No related branches found
No related tags found
No related merge requests found
Pipeline #146025 failed
Showing with 49 additions and 1294 deletions
README 0 → 100644
Project Name: Vault Data Retrieval Tool
Description:
This tool is designed to retrieve secrets and secret metadata from HashiCorp Vault using the AppRole authentication method. It provides functions to fetch passwords and key data from a specified path within the Vault.
Requirements:
- Python 3.x
- pip (Python package manager)
Installation:
1. Clone the repository from https://code.europa.eu/digit-c4/dev/ansible-vault.
2. Navigate to the project directory.
3. Create a virtual environment (optional but recommended):
```
python3 -m venv venv
source venv/bin/activate
```
4. Install dependencies using the provided requirements.txt file:
```
pip install -r requirements.txt
```
Usage:
1. Ensure you have the necessary permissions and access to the HashiCorp Vault.
2. Set up the AppRole authentication method in your Vault instance. Refer to the provided links for detailed instructions.
3. Modify the code to provide your Vault URL, namespace, role ID, secret ID, mount point, and engine details.
4. EXAMPLE
sys.path.append(config_global.get('APPLICATION', 'PYTHON-LIBRARY'))
sys.path.append('/opt/auth')
from ansible-vault.vault.client import clientV
password = clientV.getPasswordByAppRole("dev/SNOW/csui", "https://sam-hcavault.cec.eu.int", "EC/DIGIT_C4_SNET_ADMIN-ACC", "role_id", "secret_id", "apps-kv", "dev")
Functionality:
- `getPasswordByAppRole(key, vault_url, namespace_used, role_id, secret_id, mount_point, engine)`: This function retrieves a password from the specified key path in the Vault.
- `getKeysData(key, vault_url, namespace_used, role_id, secret_id, mount_point, engine)`: This function retrieves key data (secret metadata) from the specified key path in the Vault.
Notes:
- Ensure that the AppRole authentication method is correctly configured and enabled in your Vault instance.
- Modify the code according to your specific Vault configuration and requirements.
- Handle errors and exceptions appropriately in your code to ensure smooth operation.
References:
- HashiCorp Vault Documentation: [https://www.vaultproject.io/docs](https://www.vaultproject.io/docs)
- HVAC Documentation: [https://hvac.readthedocs.io](https://hvac.readthedocs.io)
Author:
Marcelo teixeira
Ricardo Silva
\ No newline at end of file
#!/usr/bin/env perl
use strict;
use warnings;
use diagnostics;
use LdapNS;
my $zboub = SNET::LdapNS::PPControls->new("blah");
exit 0;
/opt/ansible/ansible-gen-role
\ No newline at end of file
/opt/ansible/ansible-json.cfg
\ No newline at end of file
/opt/ansible/ansible.cfg
\ No newline at end of file
/opt/ansible/ansible.cfg
\ No newline at end of file
/opt/ansible/bin
\ No newline at end of file
/opt/ansible/cache_plugins
\ No newline at end of file
/opt/ansible/callback_plugins
\ No newline at end of file
/opt/ansible/connection_plugins
\ No newline at end of file
/opt/ansible/filter_plugins
\ No newline at end of file
/opt/ansible/library
\ No newline at end of file
/opt/ansible/lookup_plugins
\ No newline at end of file
# https://intragate.ec.europa.eu/snet/wiki/index.php/Service_Support/Application_Management_Service/Snet_AAA/Account_creation_and_deletion_of_Snet_members
# ansible-playbook-4.7 playbook-auth-usersldap.yml --extra-vars "username=xxx scrat_user=xxx" -kK --tags "add_user/rm_user, mandatory" -u snet
# To run specific task:
# ansible-playbook-4.7 playbook-auth-usersldap.yml --extra-vars "username=xxx" -kK --tags "propagate" -u snet
---
- hosts: localhost
gather_facts: false
connection: local
vars:
whoami: 'unknown'
vars_prompt:
- name: username
prompt: "Username not specified. Please enter it"
private: no
pre_tasks:
- name: check the playbook run with ansible >= 2.9
assert:
that:
- ansible_version.major >= 2
- ansible_version.minor >= 9
fail_msg: "Please run this playbook with at least ansible 2.9."
tasks:
- name: CHECK | User is in LDAP
block:
- name: debug
debug:
msg: "The username is {{ username }}"
# getent passwd will return an non zero exit code (2), if the account do not exist in the system (ldap)
- name: grab passwd
shell: "/usr/bin/getent passwd {{ username }}"
register: register_username
delegate_to: localhost
changed_when: False
ignore_errors: yes
- name: check that user is known
fail:
msg: "Account {{ username }} is not resolved by the system. Is it created on the LDAP? Did you make a typo?"
when: register_username.rc != 0
- name: debug
debug:
msg: "The username is {{ register_username }}"
tags: add_user
- name: Check | Whoami
block:
- name: grab whoami
shell: /usr/bin/whoami
register: register_whoami
delegate_to: localhost
changed_when: False
- name: set whoami var
set_fact:
whoami: "{{register_whoami.stdout}}"
changed_when: False
- name: debug
debug:
msg: "The whoami is {{ whoami }}"
tags: always
##########
#Add user#
##########
- name: Add user on Vshare
block:
- name: set /opt/home_nas
ansible.builtin.file:
path: /opt/home_nas
state: directory
mode: '0755'
owner: root
group: root
- name: mount /opt/home_nas
mount:
path: /opt/home_nas
state: mounted
src: unityspb-vshare-lu.snmc.cec.eu.int:/fs_home
opts: defaults,rw,soft,nolock,tcp,vers=3
boot: false
fstype: nfs
- name: Create user directory in home
ansible.builtin.file:
path: /opt/home_nas/{{username}}
state: directory
mode: '0755'
owner: "{{username}}"
group: snmc
- name: Unmount /opt/home_nas
mount:
path: /opt/home_nas
state: unmounted
become: true
delegate_to: vshare-bx.snmc.cec.eu.int
tags:
- add_user
- add_user_vshare
#############
#Remove user#
#############
- name: Remove user on Vshare
block:
- name: set /opt/home_nas
ansible.builtin.file:
path: /opt/home_nas
state: directory
mode: '0755'
owner: root
group: root
- name: mount /opt/home_nas
mount:
path: /opt/home_nas
state: mounted
src: unityspb-vshare-lu.snmc.cec.eu.int:/fs_home
opts: defaults,rw,soft,nolock,tcp,vers=3
boot: false
fstype: nfs
- name: stat /opt/home_nas/{{username}}
stat:
path: /opt/home_nas/{{username}}
register: user_folder
- name: Compress and archive
shell:
cmd: tar -jcf /opt/home_nas/_OLD/{{username}}.tbz /opt/home_nas/{{username}}
when: user_folder.stat.exists
- name: Delete user directory in home
file:
path: /opt/home_nas/{{username}}
state: absent
- name: Unmount /opt/home_nas
mount:
path: /opt/home_nas
state: unmounted
become: true
delegate_to: vshare-bx.snmc.cec.eu.int
tags:
- rm_user
- rm_user_vshare
- name: Launch propagate users from Snet LDAP to SID
# 25/07
# as seen with Jeremy on 25/07 and confirmed by Ricardo on 25/08, user creation/deletion is ok , but the update fails
# -> while update of users is implemented, the flag --error-stop must not usedi
# richeju: changed var whoami to scrat_user, needs to be set in command
shell:
cmd: "/opt/auth/bin/sid_user.py -e prod --rw-user {{ scrat_user }}"
#cmd: "/opt/auth/bin/sid_user.py -e prod --rw-user {{ whoami }} --error-stop"
delegate_to: vworker4-lu.snmc.cec.eu.int
ignore_errors: yes
tags:
- mandatory
- propagate
#when: false
- name: Launch propagate SID groups from EC LDAP to SID
shell:
cmd: "/opt/auth/bin/sid_groups.py -e prod --rw-user {{ scrat_user }}"
#cmd: "/opt/auth/bin/sid_user.py -e prod --rw-user {{ whoami }} --error-stop"
delegate_to: vworker4-lu.snmc.cec.eu.int
ignore_errors: yes
tags:
- mandatory
- propagate
- name: Launch propagate users on leankit
shell:
cmd: "/opt/auth/bin/leankit_user.py"
delegate_to: vworker4-lu.snmc.cec.eu.int
ignore_errors: yes
tags:
- mandatory
- propagate
- name: Create user on Proteus
shell:
cmd: "/opt/auth/bin/synchronize_proteus_Users.pl"
delegate_to: vworker4-lu.snmc.cec.eu.int
ignore_errors: yes
tags:
- mandatory
- propagate
- name: Launch redmine LDAP sync
shell:
cmd: /usr/local/sbin/sync_users
become: true
become_method: sudo
delegate_to: "{{ item }}"
loop: "{{ groups['vredmine_prd'] }}"
tags:
- mandatory
- redmine
- name: Sync SID users with Wiki phonebook
command: "python3 {{item}}"
args:
chdir: /export/home/snet/
with_items:
- sid_2_wiki_NMT_users_single_page.py
- sid_2_wiki_NTX_users_single_page.py
- sid_2_wiki_user.py
delegate_to: vworker0-lu.snmc.cec.eu.int
become: "{{ whoami }}"
become: true
tags:
- mandatory
- sync_phonebook
#######
#EJBCA#
#######
- name: Remove user from EJBCA
block:
- name: test presence of entity in ejbca
shell: /opt/SNet/EJBCA/ejbca/bin/ejbca.sh ra findendentity --username {{username}}
delegate_to: vcertserv-lu.snmc.cec.eu.int
become: yes
become_user: snet-pki
ignore_errors: yes
register: result_ejbca
- name: Cert found
debug:
msg: "There is a certificate, Let's remove it"
when: result_ejbca.rc == 0
- name: No cert
debug:
msg: "No entity/cert found, Skipping this action"
when: result_ejbca.rc != 0
- name: Revocation
shell: /opt/SNet/EJBCA/ejbca/bin/ejbca.sh ra revokeendentity --username {{username}} -r 5
delegate_to: vcertserv-lu.snmc.cec.eu.int
become: yes
become_user: snet-pki
when: result_ejbca.rc == 0
- name: Deletion
shell: /opt/SNet/EJBCA/ejbca/bin/ejbca.sh ra delendentity --username {{username}} -force
delegate_to: vcertserv-lu.snmc.cec.eu.int
become: yes
become_user: snet-pki
when: result_ejbca.rc == 0
tags:
- rm_user
- rm_EJBCA
This diff is collapsed.
#!/usr/bin/perl
#
use strict;
use warnings;
#
use Data::Dumper;
use Config::IniFiles;
use File::Basename;
use CGI qw/:standard/;
use Spreadsheet::WriteExcel;
use Getopt::Long;
use JSON;
use utf8;
# Remove non-breaking space char
binmode( STDOUT, ":utf8" );
use Net::LDAP;
use Cache::FileCache;
# unbuffered output:
$| = 1;
BEGIN {
my $global_iniFile = new Config::IniFiles( -file => "/opt/etc/ini/global.ini" );
push( @INC, $global_iniFile->val( 'APPLICATION', 'LIBRARY' ) );
}
use SNET::access;
use SNET::common;
use SNET::dumper;
use SNET::html;
use SNET::libdb;
use SNET::ActiveDirectory;
use vars qw($verbose $debug $help $env $script $force $cli_mode $stderr);
$debug = 0;
$verbose = 0;
( $script ) = split( /\./, basename( $0 ) );
$stderr = 1;
my $title = "LDAP Group User Check";
my $function = $title;
$function =~ s/\s/_/g;
my $href = "";
my $header = h1( a( { href => "/cgi-bin/nCheck/$script.pl" }, $title ) );
my $html_msg = "";
my $global_iniFile = new Config::IniFiles( -file => "/opt/etc/ini/global.ini" );
$env = "test";
( $html_msg ) = Access_snet_script_head( $script, $global_iniFile, $ENV, $env );
#
# Global Declarations
#
my $AiniFile = new Config::IniFiles( -file => $global_iniFile->val( 'INI', 'LDAP' ) );
$html_msg .= "error value of AiniFile is undefined" . "\n" if ( !defined( $AiniFile ) );
my $ldapserver = $AiniFile->val( 'LDAP_EC', 'SERVER' );
$html_msg .= "error value of ldapserver is undefined" . "\n" if ( !defined( $ldapserver ) );
my $basedn = $AiniFile->val( 'LDAP_EC', 'BASE' );
$html_msg .= "error value of basedn is undefined" . "\n" if ( !defined( $basedn ) );
my $ldapuser = $AiniFile->val( 'LDAP_EC', 'USER' );
$html_msg .= "error value of ldapuser is undefined" . "\n" if ( !defined( $ldapuser ) );
my $ldappasswd = $AiniFile->val( 'LDAP_EC', 'PASSWORD' );
$html_msg .= "error value of ldappasswd is undefined" . "\n" if ( !defined( $ldappasswd ) );
my $cfg_ldap_server = $AiniFile->val( 'LDAP_SNET_NG', 'SERVER' );
$html_msg .= "error value of cfg_ldap_server is undefined" . "\n" if ( !defined( $cfg_ldap_server ) );
my $cfg_ldap_user = $AiniFile->val( 'LDAP_SNET_NG', 'USER' );
$html_msg .= "error value of cfg_ldap_user is undefined" . "\n" if ( !defined( $cfg_ldap_user ) );
my $cfg_ldap_passwd = $AiniFile->val( 'LDAP_SNET_NG', 'PASSWORD' );
$html_msg .= "error value of cfg_ldap_passwd is undefined" . "\n" if ( !defined( $cfg_ldap_passwd ) );
my $cfg_ldap_group_search = $AiniFile->val( 'LDAP_SNET_NG', 'GRP_SEARCH' );
$html_msg .= "error value of cfg_ldap_group_search is undefined" . "\n" if ( !defined( $cfg_ldap_group_search ) );
my $cfg_ldap_group_search_filter = $AiniFile->val( 'LDAP_SNET_NG', 'FILTER_posix' );
$html_msg .= "error value of cfg_ldap_group_search_filter is undefined" . "\n" if ( !defined( $cfg_ldap_group_search_filter ) );
my $cfg_ldap_group_attribute = $AiniFile->val( 'LDAP_SNET_NG', 'GRP_ATTRIBUTE' );
$html_msg .= "error value of cfg_ldap_group_attribute is undefined" . "\n" if ( !defined( $cfg_ldap_group_attribute ) );
my $cfg_ldap_search_scope = $AiniFile->val( 'LDAP_SNET_NG', 'SEARCH_SCOPE' );
$html_msg .= "error value of cfg_ldap_search_scope is undefined" . "\n" if ( !defined( $cfg_ldap_search_scope ) );
my $cfg_ldap_cafile = $AiniFile->val( 'LDAP_SNET_NG', 'CA' );
$html_msg .= "error value of cfg_ldap_cafile is undefined" . "\n" if ( !defined( $cfg_ldap_cafile ) );
my $all_ldap_group = $AiniFile->val( 'LDAP_EC', 'SID_GROUP' );
$html_msg .= "error value of all_ldap_group is undefined" . "\n" if ( !defined( $all_ldap_group ) );
######
my $groups = ();
@$groups = split( ',', $all_ldap_group );
my $colnames = [ 'uid', 'cn', 'departmentNumber', 'physicalDeliveryOfficeName', 'building', 'dg', 'telephoneNumber' ];
# Get all the SNet member of NS.
my $snet_member = {};
my $off_member = {};
my $group = '';
my $cache_report = new Cache::FileCache(
{
'namespace' => 'nCheck_ldap_group_check',
'cache_root' => '/opt/resources_SNet/DGtools',
'default_expires_in' => '1h',
'auto_purge_interval' => '1h'
}
);
metaprint( 'info', "init_cache cache_report" ) if $verbose;
$force = 0;
$force = 1 if ( ( defined( param( 'force' ) ) ) && ( param( 'force' ) !~ /^$/ ) && ( param( 'force' ) =~ /^Reload$/ ) );
$verbose = 1 if ( ( defined( param( 'verbose' ) ) ) && ( param( 'verbose' ) !~ /^$/ ) && ( param( 'verbose' ) =~ /^[\d\w]+$/ ) && ( param( 'verbose' ) eq 'godmode1' ) );
my $res = $cache_report->get( 'snet_member' );
if ( ( !defined $res ) || $force ) {
undef( $res );
# do action
my $ldap = Net::LDAP->new(
$cfg_ldap_server,
async => 0,
onerror => (
( $debug == 0 ) ? sub { return $_[0] } : sub {
my $message = shift;
my $error = defined( $message->error_desc ) ? $message->error_desc : $message->error();
print STDERR 'Ldap: Unable to process request: ' . $error . "\n";
return $message;
}
),
);
if ( !$ldap ) {
metaprint 'error', "Could not connect to LDAP: $cfg_ldap_server!";
exit 1;
}
metaprint( 'info', "LDAP connection completed successfully." ) if $verbose;
my $message;
eval {
print STDERR 'Starting tls' . "\n" if ( $debug );
$message = $ldap->start_tls( verify => 'require',
cafile => $cfg_ldap_cafile, );
if ( $message->is_error() ) {
metaprint( 'error', "Could not encrypt LDAP connection." );
exit 1;
}
};
if ( $@ ) {
metaprint( 'error', "Crash - Could not encrypt LDAP connection." );
exit 1;
}
eval {
print STDERR 'binding' . "\n" if ( $debug );
$message = $ldap->bind(
$cfg_ldap_user,
password => $cfg_ldap_passwd,
version => 3,
);
if ( $message->is_error() ) {
metaprint( 'error', "LDAP bind error occurred." );
exit 1;
}
};
if ( $@ ) {
metaprint( 'error', "Crash - LDAP bind error occurred" );
exit 1;
}
metaprint( 'info', "LDAP bind operation completed successfully." ) if $verbose;
my $snetcol = [ 'memberUid', 'cn' ];
$cfg_ldap_group_attribute = [ 'memberUid', 'cn' ];
$group = '|(cn=com)(cn=ss)(cn=sd)(cn=pm)(cn=net)(cn=sec)(cn=tda)(cn=mgt)(cn=pi)(cn=bcp)';
my %searchargs;
my $cfg_ldap_group_search_f = $cfg_ldap_group_search_filter;
$cfg_ldap_group_search_f =~ s/cn=REPLACE/$group/;
$searchargs{base} = $cfg_ldap_group_search;
$searchargs{scope} = $cfg_ldap_search_scope;
$searchargs{filter} = $cfg_ldap_group_search_f;
$searchargs{attrs} = $cfg_ldap_group_attribute;
metaprint( 'info', Dumper( \%searchargs ) ) if $verbose;
my $results;
eval {
print STDERR 'searching' . "\n" if ( $debug );
$results = $ldap->search( %searchargs );
if ( $results->is_error() ) {
metaprint( 'error', "LDAP search error occurred:" . $results->code . " : " . $results->error );
exit 1;
}
};
if ( $@ ) {
metaprint( 'error', "Crash - LDAP Users Search." );
exit 1;
}
my $count = $results->count;
if ( $count >= 1 ) {
foreach my $entry ( $results->entries ) {
foreach my $key ( $entry->get_value( $snetcol->[0] ) ) {
push( @{ $snet_member->{"$key"} }, $entry->get_value( $snetcol->[1] ) );
}
}
} else {
$html_msg .= "$group is an empty group.\n" . br;
}
AD_disconnect( $ldap );
print Dumper ( $snet_member ) if ( $debug );
$cache_report->set( 'snet_member', $snet_member );
} else {
$snet_member = $res;
}
# print html_rendering (Dumper ( $snet_member ) );
######
my $fromcache = 1;
my $data = [];
foreach my $group ( @{$groups} ) {
my $gdata = $cache_report->get( $group );
if ( ( !defined $gdata ) || $force ) {
undef( $gdata );
# do action
my $ldap = AD_connect( $ldapserver, $ldapuser, $ldappasswd );
my $searchquery = "(&(objectclass=*)(cudgroup=$group))";
my $filter = "cudgroup=$group";
my $results = $ldap->search( base => $basedn, filter => $filter, attrs => $colnames );
my $count = $results->count;
$html_msg .= "Total entries returned for $group: $count." . "\n" if $main::debug;
if ( $count >= 1 ) {
foreach my $entry ( $results->entries ) {
my $tmp = ();
push( @{$tmp}, $group );
foreach my $cln ( @{$colnames} ) {
push( @{$tmp}, ( $entry->get_value( $cln ) ? $entry->get_value( $cln ) : '' ) );
}
push( @{$gdata}, $tmp );
}
} else {
$html_msg .= "$group is an empty group." . br;
}
AD_disconnect( $ldap );
$cache_report->set( $group, $gdata );
}
foreach my $dd ( @{$gdata} ) {
push( @{$data}, $dd );
}
}
# taking LDAP group 'DIGIT_NS_TEAM' as reference, for the official list members.
$group = 'DIGIT_NS_TEAM';
foreach my $member (
grep { !/^$/ } map {
if ( $_->[0] eq $group ) { $_->[1] }
} map {
$_
} @$data
) {
$off_member->{$member} = 1;
}
######
# print Dumper ( $data );
my $data_users = ();
my $data_groups = ();
my $cln = ();
for ( my $i = 0 ; $i <= $#{$colnames} ; $i++ ) {
$cln->{ $colnames->[$i] } = ( $i + 1 );
}
print Dumper ( $cln );
for my $group ( @{$groups} ) {
print( "Group is $group\n" );
$data_groups->{$group} = ();
foreach my $d ( @$data ) {
next if ( $d->[0] ne $group );
# if ( $d->[1] eq 'alogin' ) {
# print Dumper( $d );
# }
#$VAR1 = [
# 'DIGIT_UNIX', group
# 'gabrigr', uid
# 'GABRIEL Gregory', cn
# 'DIGIT.C.3.004', departement
# 'DRB- D1/007E' physical delivry Office
# ];
# colnames: 'uid', 'cn', 'departmentNumber', 'physicalDeliveryOfficeName', 'building', 'dg', 'telephoneNumber'
my $member = $d->[ $cln->{'uid'} ];
if ( $group eq 'DIGIT_SNET' ) {
# Could not be part of official and SNet at the same time
next if ( defined( $off_member->{$member} ) && ( $off_member->{$member} ) );
# Should be declared in Snet LDAP system.
next if ( !defined( $snet_member->{$member} ) );
foreach my $g ( @{ $snet_member->{$member} } ) {
next if ( $g eq 'bcp' );
push( @{ $data_groups->{ $group . '_' . uc( $g ) }{'contains'} }, $member );
push( @{ $data_users->{$member}{'is_members_of'} }, $group . '_' . uc( $g ) );
}
} elsif ( $group eq 'DIGIT_SNET_PROX' ) {
if ( ( $member !~ /^j/ ) && ( $member !~ /^x/ ) ) {
# TODO open a ticket to request removal....
next;
}
}
push( @{ $data_groups->{$group}{'contains'} }, $member );
push( @{ $data_users->{$member}{'is_members_of'} }, $group );
$data_users->{$member}{'departement'} = $d->[ $cln->{'departmentNumber'} ];
$data_users->{$member}{'office'} = $d->[ $cln->{'physicalDeliveryOfficeName'} ];
$data_users->{$member}{'cn'} = $d->[ $cln->{'cn'} ];
$data_users->{$member}{'building'} = $d->[ $cln->{'building'} ];
$data_users->{$member}{'dg'} = $d->[ $cln->{'dg'} ];
$data_users->{$member}{'telephoneNumber'} = $d->[ $cln->{'telephoneNumber'} ];
# cleaning stuff
$data_users->{$member}{'building'} =~ s/\-$//;
$data_users->{$member}{'office'} =~ s/^.+\s//;
# 'office' => 'D2/058',$
if ( $data_users->{$member}{'office'} =~ /\// ) {
$data_users->{$member}{'nop'} = $data_users->{$member}{'office'};
if ( $data_users->{$member}{'office'} =~ /^([A-Za-z])/ ) {
$data_users->{$member}{'aisle'} = $1;
}
if ( $data_users->{$member}{'office'} =~ /^[A-Za-z]?(\d+)\// ) {
$data_users->{$member}{'floor'} = $1;
$data_users->{$member}{'floor'} =~ s/^0//;
}
$data_users->{$member}{'office'} =~ s/^.*\///;
}
}
}
print Dumper( $data_users );
my $json;
my $cert_data;
print Dumper ( $data_groups );
$json = JSON->new->allow_nonref;
$cert_data = $json->pretty->canonical->encode( $data_groups );
open( OUTFILE, ">/tmp/groups.json" );
print OUTFILE $cert_data;
close( OUTFILE );
print Dumper ( $data_users );
$json = JSON->new->allow_nonref;
$cert_data = $json->pretty->canonical->encode( $data_users );
open( OUTFILE, ">/tmp/users.json" );
print OUTFILE $cert_data;
close( OUTFILE );
exit 0;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment