#!/bin/bash

imagesTree() {
  jq '[
    .[] |
    {(split("@")[1]):(split("@")[0])} |
    to_entries
    ] |
  flatten |
  group_by(.key) |
  map({key:(.[0].key), value:[.[] | .value]}) |
  from_entries'
}


#######################
# policy functions

# Получить default policy из poliсy.json файла
getDefaultPolicy() {
  configPolicyFile=$1
#   configPolicyFile="$configPolicyFile"
  if [ ! -f "$configPolicyFile" ]
  then
    return
  fi
  defaultPolicy=$(jq '.default[].type' "$configPolicyFile")
  defaultPolicy=${defaultPolicy:1:-1}
  if [ -z "$defaultPolicy" ]
  then
    defaultPolicy="insecureAcceptAnything"
  fi
  echo $defaultPolicy
}

# Получить список регистраторов
# Format:
#  getRegistries configPolicyFile [json]
# Если второй параметр json - вывод в формате json
getRegistries() {
  configPolicyFile=$1
  type=$2
  req='if .transports.docker then .transports.docker | keys else [] end'
  if [ "$type" != 'json' ]
  then
    req+=" | .[]"
  fi
  jq "$req" "$configPolicyFile"
}

# Получить список регистраторов требующих подписи (type == signedBy)
# Format:
#  getSignedRegistries configPolicyFile [json]
# Если второй параметр json - вывод в формате json
getSignedRegistries() {
  configPolicyFile=$1
  type=$2
  req='if .transports.docker then [.transports.docker | to_entries[] | select(.value[].type == "signedBy") | .key] else [] end'
  if [ "$type" != 'json' ]
  then
    req+=" | .[]"
  fi
  jq "$req" "$configPolicyFile"
}

# Получить список регистраторов не требующих подписи (type == signedBy)
# Format:
#  getNotSignedRegistries configPolicyFile [json]
# Если второй параметр json - вывод в формате json
getNotSignedRegistries() {
  configPolicyFile=$1
  type=$2
  req='if .transports.docker  then [.transports.docker | to_entries[] | select(.value[].type != "signedBy") | .key] else [] end'
  if [ "$type" != 'json' ]
  then
    req+=" | .[]"
  fi
  jq "$req" "$configPolicyFile"
}

# Поолучить список траспортов, отличных от docker
# Format:
#  getForbiddenTransports configPolicyFile [json]
# Если второй параметр json - вывод в формате json
getForbiddenTransports() {
  configPolicyFile=$1
  type=$2
  req='if .transports then [.transports | to_entries[] | select(.key != "docker").key] else [] end'
  if [ "$type" != 'json' ]
  then
    req+=" | .[]"
  fi
  jq  "$req" "$configPolicyFile"
}

# Проверка конфигурацию policy.json
checkPolicyConfig() {
  configPolicyFile=$1
  tab="\t"

  export configDir=$(dirname "$configPolicyFile")
  ret="\"configDir\":\"$configDir\""

  export defaultPolicy=$(getDefaultPolicy "$configPolicyFile")
  ret+="\n,$tab\"defaultPolicy\":\"$defaultPolicy\""
  if [ -n "$DEBUG" ]
  then
    echo "defaultPolicy=$defaultPolicy"
  fi

  if [ "$defaultPolicy" != 'reject' ]
  then
    ret+="\n,$tab\"incorrectDefaultPolicy\":\"$defaultPolicy\"\n"
    if [ -n "$DEBUG" ]
    then
      echo -ne "$(gettext 'configPolicyFile has a default policy') $defaultPolicy $(gettext 'set to something other than reject')\n\n" >&2
    fi
  fi

  joinedSigStories=$(joinSigStories "$configDir")
  ret+="\n$tab,\"joinedSigStories\":$joinedSigStories\n"
  defaultSigStore=$(getDefaultSigStore "$configDir")
  if [ -z "$defaultSigStore" ]
  then
    ret+="\n$tab,\"noDefaultSigStore\":\"true\"\n"
    if [ -n "$DEBUG" ]
    then
      echo "$(gettext 'The YAML files') $configDir/registries.d/* $(gettext 'are missing the URL of the default signature keeper default-docker.lookaside')\n\n" >&2
    fi
  fi

  signedRegistries=$(getSignedRegistries "$configPolicyFile" json)
  if [ -n "$DEBUG" ]
  then
    echo "signedRegistries=$signedRegistries" >&2
  fi
  ret+="\n,$tab\"signedRegistries\":$signedRegistries"

  notSignedRegistries=$(getNotSignedRegistries "$configPolicyFile" json)
  if [ -n "$DEBUG" ]
  then
    echo "notSignedRegistries=$notSignedRegistries" >&2
  fi
  ret+="\n,$tab\"notSignedRegistries\":$notSignedRegistries"

  forbiddenTransports=$(getForbiddenTransports "$configPolicyFile" json)
  if [ -n "$DEBUG" ]
  then
    echo "forbiddenTransports=$forbiddenTransports" >&2
  fi
  ret+="\n,$tab\"forbiddenTransports\":$forbiddenTransports"
  echo -ne "$ret"
}

checkUserImages() {
  user=$1
  configPolicyFile=$2
  configDir=$(dirname "$configPolicyFile")
#   configDir=$2
  tab="\\t\\t"
  export policyFile="$configDir/policy.json"
  ret="\n$tab\"user\": \"$user\"\n"
  userPolicyConfig=$(checkPolicyConfig "$policyFile")
  ret+=",$userPolicyConfig"
  registries=$(getRegistries "$policyFile")
  if [ -z "$registries" ]
  then
    registries='""'
  fi
  n=0
  for Registry in $registries
  do
    registry=${Registry:1:-1}
    if [ $n -eq 0 ]
    then
      in="    (. | startswith(\"$registry/\"))\n"
      out="    (. | startswith(\"$registry/\") | not)\n"
    else
      in="$in    or \n    (. | startswith(\"$registry/\"))\n"
      out="$out    and \n    (. | startswith(\"$registry/\") | not)\n"
    fi
    (( n+=1 )) ||:
  done

  in="su - -c 'podman images --format json' $user 2>/dev/null |
  jq '[[.[] | if  has(\"RepoDigests\") then .RepoDigests else [.Names] end | .[]] |
map(
  select(
$in  )
) | sort| unique | .[]]' | jq .
"

  out="su - -c 'podman images --format json' $user 2>/dev/null |
  jq '[[.[] | if  has(\"RepoDigests\") then .RepoDigests else [.Names] end | .[]] |
map(
  select(
$out  )
) | sort| unique | .[]]' | jq .
"

  TMPFILE="/tmp/checkImagesSignature.$$"
  echo -ne "$in" > $TMPFILE
  in=$(sh $TMPFILE )
  echo -ne "$out" > $TMPFILE
  out=$(sh $TMPFILE)
  rm -f $TMPFILE

  if [ -n "$out" ]
  then
    outPolicyImages=$(echo -ne "$out")
    outPolicyImagesTree=$(echo -ne "$out" | imagesTree)
    if [ -n "$DEBUG" ]
    then
      echo "$(gettext 'Workstation') $HOSTNAME, $(gettext 'user') $user: $(gettext 'Images outside file policy') $policyFile:" >&2
      echo -ne "$outPolicyImages\n\n" >&2
    fi
    ret+="\n,\"outPolicyImagesTree\": $outPolicyImagesTree"
  fi

  if [ -n "$in" ]
  then
    inPolicyImages=$(echo -ne "$in")
    inPolicyImagesTree=$(echo -ne "$in"  | imagesTree)
    if [ -n "$DEBUG" ]
    then
      echo "$(gettext 'Workstation') $HOSTNAME, $(gettext 'user') $user: $(gettext 'Images according to file policy') $policyFile:" >&2
      echo -ne "$inPolicyImages\n\n" >&2
    fi
    ret+="\n,\"inPolicyImagesTree\": $inPolicyImagesTree"
  fi

#  inCorrectImages=
  images=$in
  if [ -n "$CHECKALLIMAGES" ]
  then
    images="$in $out"
  fi

  notSignedImages=
  signedImages=
  for image in $(echo "$images" | jq '.[]')
  do
    sigStoreURL=$(getSigStoreURLForImage "$configDir" "${image:1:-1}")
    sigStoreURL="$sigStoreURL/signature-1"
    if wget -q -O "$TMPFILE" "$sigStoreURL" 2>/dev/null
    then
      signedImages+="${signedImages:+,}$image"
    else
      notSignedImages+="${notSignedImages:+,}$image"
    fi
  done
  rm -f $TMPFILE
  signedImages="[$signedImages]"
  notSignedImages="[$notSignedImages]"
#   ret+="\n,\"signedImages\": $signedImages"
#   ret+="\n,\"notSignedImages\": $notSignedImages"
  signedImagesTree=$(echo "$signedImages" | imagesTree)
  notSignedImagesTree=$(echo "$notSignedImages" | imagesTree)
  ret+="\n,\"signedImagesTree\": $signedImagesTree"
  ret+="\n,\"notSignedImagesTree\": $notSignedImagesTree"

  echo -ne "$ret";
}

#######################
# SIGSTORE functions
#  Объединить все yaml-файлы в директории ${1}/registries.d и олучить единый JSON
joinSigStories() {
  set +o noglob
  ret=
  configDir="${1}/registries.d"
  yamlFile="$configDir/default.yaml"
  if [ ! -f "$yamlFile" ]
  then
    echo "{}"
    return
  fi
  yaml=$(yq . "$yamlFile")
  ret="${ret}${ret:+,}$yaml"
  ret="[$ret]"
  echo "$ret" | jq 'reduce .[] as $item ({}; . *= $item)'
}

getDefaultSigStore()  {
  configDir=$1
  joinSigStories=$(joinSigStories "$configDir")
  lookaside=$(echo "$joinSigStories" | yq '."default-docker".lookaside')
  if [ "$lookaside" != 'null' ]
  then
    echo "$lookaside"
  fi
}


#  Вернуть Sigstore для образа или его части включая registry, которые наиболее полно соответсвует описание в файлах $1/registries.d/*.yaml
#  Возвращается JSON-список из одного (lookaside) или двух элементов
#  {
#    "lookaside": "http://...",
#    "sigstore": "http://..."
#  }
getSigStoreList() {
  configDir=$1
  image=$2
  # Объединить описания в файлах $configDir/registries.d/*.yaml
  joinSigStories=$(joinSigStories "$configDir")
  # Отсортировать .docker.key по уменьшению длины ключа, выбрать первый соотвествующий $image
  echo "$joinSigStories" | yq "if .docker then [.docker | to_entries | sort_by(.key|(-length))[] | select(.key == \"$image\"[0:.key|length])][0].value else empty end"
}


#  Вернуть URL файлов каталога подписей в файлах $1/registries.d/*.yaml URL-адрес каталога подписей в SigStore
#  Каталог содержит файлы подписей - signature-1, signature-, ...
getSigStoreURLForImage() {
  configDir=$1
  image=$2
  joinSigStories=$(joinSigStories "$configDir")
  registry=$(echo "\"$image\"" | jq '. | split("/")[0]')
  url=$(echo "\"$image\"" | jq '. | split("/")[1:] | join("/")' | tr ':' '=')
  if [ -n "$registry" ]
  then
    lookaside=$(getSigStoreList "$configDir" "$image" | jq '.lookaside | rtrimstr("/")')
  else
    lookaside=$(getDefaultSigStore "$configDir")
  fi
  url=$(echo "$lookaside/$url" | tr -d '"')
  echo "$url"
}

getGroup() {
  id -g -n "$1"
}

# Функция выводит список групп позователя включая основную группу
getGroups() {
  ret="\"$(id -g -n "$1")\""
  for group in $(id -G -n "$1")
  do
    ret+=",\"$group\""
  done
  echo "[$ret]"
}

#########################################################
checkUsersConfig() {
  set +o noglob
  ret=
  user="root"
  policyFile="/etc/containers/policy.json"
  checkPolicyResult=$(checkPolicyConfig $policyFile)
  checkPolicyResult+=",\"group\":\"$(getGroup $user)\""
  checkPolicyResult+=",\"groups\":$(getGroups $user)"
  ret+="\n{\"user\":\"$user\",$checkPolicyResult}"
  for userDir in /home/*
  do
    if [ ! -d "$userDir/.local/share/containers/storage/overlay" ]
    then
      continue
    fi
    policyFile="$userDir/.config/containers/policy.json"
    if [ ! -f "$policyFile" ];
    then
      policyFile="/etc/containers/policy.json"
    fi
    checkPolicyResult=$(checkPolicyConfig "$policyFile")
    user=$(basename "$userDir")
    checkPolicyResult+=",\"group\":\"$(getGroup "$user")\""
    checkPolicyResult+=",\"groups\":$(getGroups "$user")"
    ret+="\n,{\"user\":\"$user\",$checkPolicyResult}"
#       echo "RET=$ret"
  done
  ret="[$ret]"
  echo -ne "$ret"
}

checkUsersImages() {
  set +o noglob
  ret=
  user="root"
  policyFile="/etc/containers/policy.json"
  userImages=$(checkUserImages $user $policyFile)
  guserImages+=",\"group\":\"$(getGroup $user)\""
  userImages+=",\"groups\":$(getGroups $user)"
  ret+="\n{\"user\":\"$user\",$userImages}"
  for userDir in /home/*
  do
    if [ ! -d "$userDir/.local/share/containers/storage/overlay" ]
    then
      continue
    fi
    policyFile="$userDir/.config/containers/policy.json"
    if [ ! -f "$policyFile" ];
    then
      policyFile="/etc/containers/policy.json"
    fi
    user=$(basename "$userDir")
    if id -G -n $user | grep podman_dev >/dev/null 2>&1
    then
      continue
    fi
    userImages=$(checkUserImages "$user" "$policyFile")
    userImages+=",\"group\":\"$(getGroup "$user")\""
    userImages+=",\"groups\":$(getGroups "$user")"
    ret+="\n,{\"user\":\"$user\",$userImages}"
  done
  ret="[$ret]"
  echo -ne "$ret"
}
