Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .checkov.yaml
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
skip-check:
# CKV
- CKV_AWS_18 # INFO "Ensure the S3 bucket has access logging enabled"
- CKV_AWS_28 # HIGH "Ensure DynamoDB point in time recovery (backup) is enabled"
- CKV_AWS_59 # LOW "Ensure there is no open access to back-end resources through API"
- CKV_AWS_109 # LOW "Ensure IAM policies does not allow permissions management / resource exposure without constraints"
- CKV_AWS_111 # LOW "Ensure IAM policies does not allow write access without constraints"
- CKV_AWS_116 # LOW "Ensure that AWS Lambda function is configured for a Dead Letter Queue(DLQ)"
- CKV_AWS_117 # LOW "Ensure that AWS Lambda function is configured inside a VPC"
- CKV_AWS_119 # INFO "Ensure DynamoDB Tables are encrypted using a KMS Customer Managed CMK"
- CKV_AWS_120 # LOW "Ensure API Gateway caching is enabled"
- CKV_AWS_144 # LOW "Ensure that S3 bucket has cross-region replication enabled"
- CKV_AWS_158 # LOW "Ensure that CloudWatch Log Group is encrypted by KMS"
- CKV_AWS_173 # LOW "Check encryption settings for Lambda environmental variable"
- CKV_AWS_225 # LOW "Ensure API Gateway method setting caching is enabled"
- CKV_AWS_272 # HIGH "Ensure AWS Lambda function is configured to validate code-signing"
- CKV_AWS_283 # HIGH "Ensure no IAM policies documents allow ALL or any AWS principal permissions to the resource"
- CKV_AWS_297 # HIGH "Ensure EventBridge Scheduler Schedule uses Customer Managed Key (CMK)"
- CKV_AWS_337 # HIGH "Ensure SSM parameters are using KMS CMK"
- CKV_AWS_338 # INFO "Ensure CloudWatch log groups retains logs for at least 1 year"
- CKV_AWS_356 # HIGH "Ensure no IAM policies documents allow "*" as a statement's resource for restrictable actions"
# CKV2
- CKV2_AWS_16 # INFO "Ensure that Auto Scaling is enabled on your DynamoDB tables"
- CKV2_AWS_29 # MEDIUM "Ensure public API gateway are protected by WAF"
- CKV2_AWS_51 # LOW "Ensure AWS API Gateway endpoints uses client certificate authentication"
- CKV2_AWS_73 # INFO "Ensure AWS SQS uses CMK not AWS default keys for encryption"
48 changes: 37 additions & 11 deletions apps/poweron/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,55 @@ package config

import (
"context"
"fmt"
"os"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ssm"
)

const (
EnvAPIToken = "TELEGRAM_APITOKEN"
EnvSSMParamAPIToken = "SSM_PARAM_TELEGRAM_APITOKEN"
EnvSSMParamCache = "SSM_PARAM_CACHE"
EnvDynamoDBSubscriptionsTable = "DYNAMODB_TABLE_SUBSCRIPTIONS"

PowerScheduleCacheTTL = 5 * time.Minute
)

type Config struct {
AWSConfig aws.Config
TelegramAPIToken string
SSMParamTelegramAPIToken string
SSMParamPowerScheduleCache string
SSMParamCache string
DynamoDBSubscriptionsTable string
PowerScheduleCacheTTL time.Duration
}

func LoadAppConfig() Config {
return Config{
TelegramAPIToken: os.Getenv("TELEGRAM_APITOKEN"),
SSMParamTelegramAPIToken: os.Getenv("SSM_PARAM_TELEGRAM_APITOKEN"),
SSMParamPowerScheduleCache: os.Getenv("SSM_PARAM_POWERON_CACHE"),
PowerScheduleCacheTTL: 5 * time.Minute,
func LoadConfig(ctx context.Context) (*Config, error) {
awsConfig, err := config.LoadDefaultConfig(ctx)
if err != nil {
return nil, fmt.Errorf("failed to load AWS config: %w", err)
}

apiToken := os.Getenv(EnvAPIToken)
if apiToken == "" {
response, err := ssm.NewFromConfig(awsConfig).GetParameter(ctx, &ssm.GetParameterInput{
Name: aws.String(os.Getenv(EnvSSMParamAPIToken)),
WithDecryption: aws.Bool(true),
})
if err != nil {
return nil, fmt.Errorf("failed to get Telegram API token from SSM parameter: %w", err)
}
apiToken = *response.Parameter.Value
}
}

func LoadAWSConfig(ctx context.Context) (aws.Config, error) {
return config.LoadDefaultConfig(ctx)
return &Config{
AWSConfig: awsConfig,
TelegramAPIToken: apiToken,
SSMParamCache: os.Getenv(EnvSSMParamCache),
DynamoDBSubscriptionsTable: os.Getenv(EnvDynamoDBSubscriptionsTable),
PowerScheduleCacheTTL: PowerScheduleCacheTTL,
}, nil
}
4 changes: 4 additions & 0 deletions apps/poweron/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ require (
github.com/aws/aws-lambda-go v1.51.0
github.com/aws/aws-sdk-go-v2 v1.41.0
github.com/aws/aws-sdk-go-v2/config v1.32.5
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.20.29
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.5
github.com/aws/aws-sdk-go-v2/service/ssm v1.67.7
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
)
Expand All @@ -16,7 +18,9 @@ require (
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.32.9 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.16 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 // indirect
Expand Down
8 changes: 8 additions & 0 deletions apps/poweron/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ github.com/aws/aws-sdk-go-v2/config v1.32.5 h1:pz3duhAfUgnxbtVhIK39PGF/AHYyrzGEy
github.com/aws/aws-sdk-go-v2/config v1.32.5/go.mod h1:xmDjzSUs/d0BB7ClzYPAZMmgQdrodNjPPhd6bGASwoE=
github.com/aws/aws-sdk-go-v2/credentials v1.19.5 h1:xMo63RlqP3ZZydpJDMBsH9uJ10hgHYfQFIk1cHDXrR4=
github.com/aws/aws-sdk-go-v2/credentials v1.19.5/go.mod h1:hhbH6oRcou+LpXfA/0vPElh/e0M3aFeOblE1sssAAEk=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.20.29 h1:dQFhl5Bnl/SK1EVpgElK5dckAE+lMHXnl5WCeRvNEG0=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.20.29/go.mod h1:BtBP1TCx5BTCh1uTVXpo3b/odnRECBpZdL5oHQarJJs=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc=
Expand All @@ -14,8 +16,14 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.5 h1:mSBrQCXMjEvLHsYyJVbN8QQlcITXwHEuu+8mX9e2bSo=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.53.5/go.mod h1:eEuD0vTf9mIzsSjGBFWIaNQwtH5/mzViJOVQfnMY5DE=
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.32.9 h1:mB79k/ZTxQL4oDPxLAf2rhcUEvXlHkj3loGA2O9xREk=
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.32.9/go.mod h1:wXQmLDkBNh60jxAaRldON9poacv+GiSIBw/kRuT/mtE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.16 h1:8g4OLy3zfNzLV20wXmZgx+QumI9WhWHnd4GCdvETxs4=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.11.16/go.mod h1:5a78jwLMs7BaesU0UIhLfVy2ZmOEgOy6ewYQXKTD37Q=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ=
Expand Down
79 changes: 25 additions & 54 deletions apps/poweron/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,31 @@ package main
import (
"context"
"encoding/json"
"fmt"
"log"

"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ssm"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/rvolykh/telegram-bot/apps/poweron/config"
"github.com/rvolykh/telegram-bot/apps/poweron/operations"
"github.com/rvolykh/telegram-bot/apps/poweron/reply"
)

type LambdaFunction struct {
SSMClient *ssm.Client
Config config.Config
Config *config.Config
}

func (f *LambdaFunction) Handler(ctx context.Context, sqsEvent events.SQSEvent) error {
log.Printf("Received SQS Event with %d records", len(sqsEvent.Records))

apiToken, err := f.getAPIToken(ctx)
if err != nil {
return fmt.Errorf("failed to get API token: %w", err)
if len(sqsEvent.Records) > 0 {
return f.handleSQS(ctx, sqsEvent.Records)
}

telegram, err := reply.NewTelegram(apiToken)
if err != nil {
return fmt.Errorf("failed to create telegram: %w", err)
}
return f.handleSchedule(ctx)
}

for i, record := range sqsEvent.Records {
func (f *LambdaFunction) handleSQS(ctx context.Context, records []events.SQSMessage) error {
for i, record := range records {
log.Printf("Processing record %d:", i+1)
log.Printf(" Message ID: %s", record.MessageId)
log.Printf(" Receipt Handle: %s", record.ReceiptHandle)
Expand All @@ -61,57 +54,50 @@ func (f *LambdaFunction) Handler(ctx context.Context, sqsEvent events.SQSEvent)

args := update.Message.CommandArguments()
switch args {
case "":
err = operations.ShowMainMenu(ctx, telegram, chatID)
if err != nil {
log.Printf("Error showing main menu: %v", err)
continue
}
log.Printf("Main menu sent to chat: %d", chatID)

case "Сьогодні":
err = operations.Today(ctx, f.Config, telegram, f.SSMClient, chatID)
if err != nil {
if err := operations.Today(ctx, f.Config, chatID); err != nil {
log.Printf("Error showing today schedule: %v", err)
continue
}
log.Printf("Today schedule sent to chat: %d", chatID)

case "Завтра":
err = operations.Tomorrow(ctx, f.Config, telegram, f.SSMClient, chatID)
if err != nil {
if err := operations.Tomorrow(ctx, f.Config, chatID); err != nil {
log.Printf("Error showing tomorrow schedule: %v", err)
continue
}
log.Printf("Tomorrow schedule sent to chat: %d", chatID)

case "Підписатись":
err = operations.ShowSelectGroupMenu(ctx, telegram, chatID)
if err != nil {
if err := operations.ShowSelectGroupMenu(ctx, f.Config, chatID); err != nil {
log.Printf("Error subscribing: %v", err)
continue
}
log.Printf("Select group menu sent to chat: %d", chatID)

case "Відписатись":
err = operations.Unsubscribe(ctx, f.Config, telegram, f.SSMClient, chatID)
if err != nil {
if err := operations.Unsubscribe(ctx, f.Config, chatID); err != nil {
log.Printf("Error unsubscribing: %v", err)
continue
}
log.Printf("Unsubscribed from chat: %d", chatID)

case "":
if err := operations.ShowMainMenu(ctx, f.Config, chatID); err != nil {
log.Printf("Error showing main menu: %v", err)
continue
}
log.Printf("Main menu sent to chat: %d", chatID)

case "Закрити":
err = operations.CloseMenu(ctx, telegram, chatID)
if err != nil {
if err := operations.CloseMenu(ctx, f.Config, chatID); err != nil {
log.Printf("Error closing menu: %v", err)
continue
}
log.Printf("Closed menu in chat: %d", chatID)

case "1.1", "1.2", "2.1", "2.2", "3.1", "3.2", "4.1", "4.2", "5.1", "5.2", "6.1", "6.2":
err = operations.Subscribe(ctx, f.Config, telegram, f.SSMClient, chatID, args)
if err != nil {
if err := operations.Subscribe(ctx, f.Config, chatID, args); err != nil {
log.Printf("Error showing group schedule: %v", err)
continue
}
Expand All @@ -126,30 +112,15 @@ func (f *LambdaFunction) Handler(ctx context.Context, sqsEvent events.SQSEvent)
return nil
}

func (f *LambdaFunction) getAPIToken(ctx context.Context) (string, error) {
if f.Config.TelegramAPIToken != "" {
return f.Config.TelegramAPIToken, nil
}

apiToken, err := f.SSMClient.GetParameter(ctx, &ssm.GetParameterInput{
Name: aws.String(f.Config.SSMParamTelegramAPIToken),
WithDecryption: aws.Bool(true),
})
if err != nil {
return "", fmt.Errorf("failed to get SSM parameter: %w", err)
}
return *apiToken.Parameter.Value, nil
func (f *LambdaFunction) handleSchedule(ctx context.Context) error {
return operations.DeliverScheduleUpdates(ctx, f.Config)
}

func main() {
cfg, err := config.LoadAWSConfig(context.TODO())
cfg, err := config.LoadConfig(context.TODO())
if err != nil {
log.Printf("unable to load AWS config: %v", err)
log.Printf("unable to load config: %v", err)
}

fn := &LambdaFunction{
SSMClient: ssm.NewFromConfig(cfg),
Config: config.LoadAppConfig(),
}
lambda.Start(fn.Handler)
lambda.Start((&LambdaFunction{Config: cfg}).Handler)
}
28 changes: 16 additions & 12 deletions apps/poweron/operations/menu.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"

tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/rvolykh/telegram-bot/apps/poweron/config"
"github.com/rvolykh/telegram-bot/apps/poweron/reply"
)

Expand Down Expand Up @@ -56,26 +57,29 @@ var (
keyboardCloseMenu = tgbotapi.NewRemoveKeyboard(true)
)

func ShowMainMenu(ctx context.Context, t *reply.Telegram, chatID int64) error {
err := t.SendMenu(ctx, chatID, "Оберіть операцію", keyboardMainMenu)
func ShowMainMenu(ctx context.Context, cfg *config.Config, chatID int64) error {
t, err := reply.NewTelegram(cfg)
if err != nil {
return fmt.Errorf("failed to send main menu: %w", err)
return fmt.Errorf("failed to create telegram client: %w", err)
}
return nil

return t.SendMenu(ctx, chatID, "Оберіть операцію", keyboardMainMenu)
}

func ShowSelectGroupMenu(ctx context.Context, t *reply.Telegram, chatID int64) error {
err := t.SendMenu(ctx, chatID, "Оберіть групу", keyboardGroupsMenu)
func ShowSelectGroupMenu(ctx context.Context, cfg *config.Config, chatID int64) error {
t, err := reply.NewTelegram(cfg)
if err != nil {
return fmt.Errorf("failed to send select group menu: %w", err)
return fmt.Errorf("failed to create telegram client: %w", err)
}
return nil

return t.SendMenu(ctx, chatID, "Оберіть групу", keyboardGroupsMenu)
}

func CloseMenu(ctx context.Context, t *reply.Telegram, chatID int64) error {
err := t.SendMenu(ctx, chatID, "Закрито", keyboardCloseMenu)
func CloseMenu(ctx context.Context, cfg *config.Config, chatID int64) error {
t, err := reply.NewTelegram(cfg)
if err != nil {
return fmt.Errorf("failed to send close menu: %w", err)
return fmt.Errorf("failed to create telegram client: %w", err)
}
return nil

return t.SendMenu(ctx, chatID, "Закрито", keyboardCloseMenu)
}
Loading