Terraform issue with using dynamic block for ingress rules on security group - terraform-provider-aws

I am basically trying to create a rule to allow ssh, rdp and http traffic from ip address x.x.x.x/32 (where x.x.x.x is a real ip address).
this is my tf file
resource "aws_security_group" "allow_internet" {
name = "allow_internet"
description = "allow_internet_from_my_connection"
vpc_id = aws_vpc.dee_vpc.id
dynamic "ingress" {
for_each = var.sg_protocols
iterator = protocol
content {
from_port = 0
to_port = 0
protocol = protocol.value
cidr_blocks = ["x.x.x.x/32"]
}
}
And this is my variable
variable "sg_protocols" {
type = list(string)
description = "list of ingress ports"
default = ["rdp", "ssh", "rdp"]
}
I get the below error
λ terraform plan
Error: Invalid number literal
on securitygroup.tf line 14, in resource "aws_security_group" "allow_internet":
14: cidr_blocks = [x.x.x.x/32]
Failed to recognize the value of this number literal.

That is the wrong usage of:
to_port
from_port
protocol
You can refer to https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group for specific usage.
If you want to use dynamic blocks, you will need to create a more complex object to hold the value of the 3 parameters mentioned above.
And the syntax of the dynamic block is also incorrect.
Try this:
resource "aws_security_group" "allow_internet" {
name = "allow_internet"
description = "allow_internet_from_my_connection"
vpc_id = aws_vpc.test_vpc.id
dynamic "ingress" {
for_each = var.sg_protocols
content {
from_port = ingress.value["from_port"]
to_port = ingress.value["to_port"]
protocol = ingress.value["protocol"]
cidr_blocks = ["10.10.10.10/32"]
}
}
}
variable "sg_protocols" {
type = list(object({
from_port = number
to_port = number
protocol = string
}))
default = [
{
from_port = 80
to_port = 80
protocol = "tcp"
},
{
from_port = 22
to_port = 22
protocol = "tcp"
}
]
}

Related

Azure Bicep template deployment failure with error: "The value of parameter linuxConfiguration.ssh.publicKeys.path is invalid.\"

I created a bicep templated to deploy on Azure (using bash) a linux vm with the associated resources (nic, vnet, subnet, publicIP). Part of the deployment fails; where all the associated resources are deployed but the vm itself fails to deploy.
The error is as follows:
{"status":"Failed","error":{"code":"DeploymentFailed","message":"At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/DeployOperations for usage details.","details":[{"code":"BadRequest","message":"{\r\n "error": {\r\n "code": "InvalidParameter",\r\n "message": "Destination path for SSH public keys is currently limited to its default value /home/user/.ssh/authorized_keys due to a known issue in Linux provisioning agent.",\r\n "target": "linuxConfiguration.ssh.publicKeys.path"\r\n }\r\n}"}]}}
The bicep template provided by microsoft uses the path: '/home/${adminUsername}/.ssh/authorized_keys'
I can't seem to figure out a way for it to deploy. Any assistance would greatly appreciated.
Here is the bicep file that causes the error:
#description('Name of the VM')
param vmName string = 'stagingLinuxVM'
#description('location for all resources')
param location string = resourceGroup().location
#description('vm sizes allowed RAM & temp storage in GiB per tier (respectively): 0.5/4; 1/4; 2/4; 4/8; 8/16')
#allowed([
'Standard_B1s'
'Standard_B1ms'
'Standard_B2s'
'Standard_B2ms'
])
param vmSize string = 'Standard_B1s'
#description('Username for the VM')
param adminUsername string
#description('SSH Key for the Virtual Machine')
#secure()
param adminPasswordKey string
#description('name of VNET')
param virtualNetworkName string = 'vnet'
#description('name of the subnet in the virtual network')
param subnetName string = 'Subnet'
param dnsLabelPrefix string = toLower('${vmName}-${uniqueString(resourceGroup().id)}')
var osDiskType = 'Standard_LRS'
var networkInterfaceName = '${vmName}nic'
var addressPrefix = '10.1.0.0/16'
var publicIPAddressName = '${vmName}PublicIP'
var subnetAddressPrefix = '10.1.0.0/24'
var linuxConfiguration = {
disablePasswordAuthentication: true
provisionVMAgent: true
ssh: {
publicKeys: [
{
path: '/home/${adminUsername}/.ssh/authorized_keys'
keyData: adminPasswordKey
}
]
}
}
resource nic 'Microsoft.Network/networkInterfaces#2021-08-01' = {
name: networkInterfaceName
location: location
properties: {
ipConfigurations: [
{
name: 'ipconfig1'
properties: {
subnet: {
id: subnet.id
}
privateIPAllocationMethod: 'Dynamic'
publicIPAddress: {
id: publicIP.id
}
}
}
]
}
}
resource vnet 'Microsoft.Network/virtualNetworks#2021-08-01' = {
name: virtualNetworkName
location: location
properties: {
addressSpace: {
addressPrefixes: [
addressPrefix
]
}
}
}
resource subnet 'Microsoft.Network/virtualNetworks/subnets#2021-08-01' = {
parent: vnet
name: subnetName
properties: {
addressPrefix: subnetAddressPrefix
privateEndpointNetworkPolicies: 'Enabled'
privateLinkServiceNetworkPolicies: 'Enabled'
}
}
resource publicIP 'Microsoft.Network/publicIPAddresses#2021-08-01' = {
name: publicIPAddressName
location: location
sku: {
name: 'Basic'
}
properties: {
publicIPAllocationMethod: 'Dynamic'
publicIPAddressVersion: 'IPv4'
dnsSettings: {
domainNameLabel: dnsLabelPrefix
}
idleTimeoutInMinutes: 4
}
}
resource vm 'Microsoft.Compute/virtualMachines#2021-11-01' = {
name: vmName
location: location
properties: {
hardwareProfile: {
vmSize: vmSize
}
osProfile: {
adminPassword: adminPasswordKey
adminUsername: adminUsername
computerName: vmName
linuxConfiguration: linuxConfiguration
}
storageProfile: {
imageReference: {
offer: 'UbuntuServer'
publisher: 'Canonical'
sku: '18.04-LTS'
version: 'latest'
}
osDisk: {
createOption: 'FromImage'
deleteOption: 'Delete'
diskSizeGB: 32
osType: 'Linux'
managedDisk: {
storageAccountType: osDiskType
}
}
}
networkProfile: {
networkInterfaces: [
{
id: nic.id
}
]
}
}
}
output adminUsername string = adminUsername
output hostname string = publicIP.properties.dnsSettings.fqdn
output sshComand string = 'ssh ${adminUsername}#${publicIP.properties.dnsSettings.fqdn}'

Writing logs with cloud logging

I'm having problems with using google-cloud/logging. My objective is to write to a file which is to be created weekly, and i have previously managed to do that. However since yesterday I kept getting this error:
Error: 3 INVALID_ARGUMENT: A monitored resource must be specified for each log entry.
So I updated the google-cloud/logging to the latest version(5.2.2) after reading up about a similar issue of monitored resource not being set automatically. Which did take care of that error, however the logs are not showing up in logs viewer after that change.
My code for the logger utility is as follows
const { Logging } = require('#google-cloud/logging');
exports.LoggingUtil = class LoggingUtil {
constructor(context){
var LogMetadata = {
severity: "INFO",
type: "gce_instance",
labels: {
function_name: process.env.FUNCTION_NAME,
project: process.env.GCLOUD_PROJECT,
region: process.env.FUNCTION_REGION
}
}
this.metadata = {
LogMetadata,
labels: {
execution_id: `${context.eventId}`
}
}
const logging = new Logging();
this.log = logging.log(this.getWeekStamp());
}
getWeekStamp(){
const environmentName = constants.environment.name;
var d = new Date();
var day = d.getDay(),
diff = d.getDate() - day + (day == 0 ? -6:1);
date = new Date(d.setDate(diff)).toLocaleDateString('en-US', { day: '2-digit', month: 'short', year: 'numeric'});
date = date.replace(" ", "-");
return `${date.replace(", ","-")}-week-${environmentName}`;
}
write(text){
var entry = this.log.entry(this.metadata, text);
this.log.write(entry);
}
}
What have I done wrong with this? Any help is appreciated 🙏
I think that you error is related with the way to obtain the metadata variable, because is creating an malformed object that is not readable by the log viewer.
In your method constructor you are creating a metadata object similar to this:
{ "LogMetadata":{
"severity":"INFO",
"type":"gce_instance",
"labels":{
"function_name":process.env.FUNCTION_NAME,
"project":process.env.GCLOUD_PROJECT,
"region":process.env.FUNCTION_REGION
}
},
"labels":{
"execution_id":`${context.eventId}`
}}
that is not a valid MonitoredResource, you can change your code in order to create a valid MonitoredResource for example
var LogMetadata = {
severity: "INFO",
type: "gce_instance",
labels: {
function_name: process.env.FUNCTION_NAME,
project: process.env.GCLOUD_PROJECT,
region: process.env.FUNCTION_REGION
}
}
this.metadata = LogMetadata
this.metadata.labels["execution_id"] = `${context.eventId}`
Example result object
{"severity":"INFO",
"type":"gce_instance",
"labels":{"function_name":process.env.FUNCTION_NAME,
"project":process.env.GCLOUD_PROJECT,
"region": process.env.FUNCTION_REGION,
"execution_id":`${context.eventId}`}
}
Additionally, you can check this example file as a reference to write logs using nodeJS.

How to add multiple actions to Swashbuckle Document Path pointing to one endpoint?

I try to describe my asp.net Web API OAuth endpoint in swagger using Swashbuckle 5.6.0 and tried this solution:
How to show WebApi OAuth token endpoint in Swagger
My problem is, that the URL of receiving an access token and getting a new one by refresh token is the same in asp.net OAuth Authorization Server.
Adding the second URL to the Swagger Document Path fails due to the fact, that "paths" is a IDictionary<string, PathItem>.
public class AuthTokenOperation : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
{
// get the Token Endpoint from Config
var endpoint = Helpers.GetAppSetting("TokenEndPoint");
// Access Token
swaggerDoc.paths.Add(endpoint, new PathItem
{
post = new Operation
{
tags = new List<string> { "AccessToken" },
consumes = new string[] { "application/x-www-form-url-encoded" },
produces = new string[] { "application/json" },
parameters = new List<Parameter>
{
new Parameter
{
type = "string",
name = "username",
required = true,
#in = "formData"
},
new Parameter
{
type = "string",
name = "password",
required = true,
#in = "formData"
},
new Parameter
{
type = "string",
name = "grant_type",
required = true,
#in = "formData"
},
new Parameter
{
type = "string",
name = "client_id",
required = true,
#in = "formData"
},
new Parameter
{
type = "string",
name = "client_secret",
required = true,
#in = "formData"
}
}
}
});
// Refresh Token
swaggerDoc.paths.Add(endpoint, new PathItem
{
post = new Operation
{
tags = new List<string> { "AccessToken" },
consumes = new string[] { "application/x-www-form-url-encoded" },
produces = new string[] { "application/json" },
parameters = new List<Parameter>
{
new Parameter
{
type = "string",
name = "grant_type",
required = true,
#in = "formData"
},
new Parameter
{
type = "string",
name = "client_id",
required = true,
#in = "formData"
},
new Parameter
{
type = "string",
name = "client_secret",
required = true,
#in = "formData"
},
new Parameter
{
type = "string",
name = "refresh_token",
required = true,
#in = "formData"
}
}
}
});
}
}
Is there any possibility to describe two api methods pointing to the same endpoint, just using diffrent parameters?
Like shown here: https://api.gettyimages.com/swagger/ui/index#!/OAuth
Finally the hint "adding something meaningless" worked for our use case.
I additionally added a new model class AuthServerResponseModel, where the response of the auth request is mapped in.
public class AuthServerResponseModel
{
public string access_token { get; set; }
public string token_type { get; set; }
public int expires_in { get; set; }
public string refresh_token { get; set; }
public string audience { get; set; }
}
To make this object be known in Swagger, the class has to be added to the SchemaRegistry.
After that I could use the "#ref" tag in the response schema to declare the response type of my auth request.
public class AuthTokenOperation : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer)
{
schemaRegistry.GetOrRegister(typeof(AuthServerResponseModel));
// get the Token Endpoint from Config
string endpoint = "URL-To-The-OAuth-Endpoint";
// Access Token
swaggerDoc.paths.Add(endpoint + "#AccessToken", new PathItem
{
post = new Operation
{
operationId = "AccessToken",
tags = new List<string> { "Token" },
consumes = new string[] { "application/x-www-form-url-encoded" },
produces = new string[] { "application/json" },
parameters = new List<Parameter>
{
new Parameter
{
type = "string",
name = "username",
required = true,
#in = "formData"
},
new Parameter
{
type = "string",
name = "password",
required = true,
#in = "formData"
},
new Parameter
{
type = "string",
name = "grant_type",
required = true,
#in = "formData"
},
new Parameter
{
type = "string",
name = "client_id",
required = true,
#in = "formData"
},
new Parameter
{
type = "string",
name = "client_secret",
required = true,
#in = "formData"
},
new Parameter
{
type = "string",
name = "DeviceId",
required = false,
#in = "formData"
}
},
responses = new Dictionary<string, Response>()
{
{ "200", new Response() { description = "Ok", schema = new Schema() { type = "object", #ref = "#/definitions/AuthServerResponseModel" } } },
{ "400", new Response() { description = "BadRequest" } },
{ "404", new Response() { description = "NotFound" } }
}
}
});
// Refresh Token
swaggerDoc.paths.Add(endpoint + "#RefreshToken", new PathItem
{
post = new Operation
{
operationId = "RefreshToken",
tags = new List<string> { "Token" },
consumes = new string[] { "application/x-www-form-url-encoded" },
produces = new string[] { "application/json" },
parameters = new List<Parameter>
{
new Parameter
{
type = "string",
name = "grant_type",
required = true,
#in = "formData"
},
new Parameter
{
type = "string",
name = "client_id",
required = true,
#in = "formData"
},
new Parameter
{
type = "string",
name = "client_secret",
required = true,
#in = "formData"
},
new Parameter
{
type = "string",
name = "refresh_token",
required = true,
#in = "formData"
}
},
responses = new Dictionary<string, Response>()
{
{ "200", new Response() { description = "Ok", schema = new Schema() { type = "object", #ref = "#/definitions/AuthServerResponseModel" } } },
{ "400", new Response() { description = "BadRequest" } },
{ "404", new Response() { description = "NotFound" } }
}
}
});
}
}
Automatic Client generation using Swagger Codegen works well now.
The paths is a dictionary in swashbuckle:
public class SwaggerDocument
{
public readonly string swagger = "2.0";
public Info info;
...
public IDictionary<string, PathItem> paths;
...
}
that is why the exception "key has already been added to the collection"
On swagger we follow the Open Api Specification, and that path is a patterned field:
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#patterned-fields
And they clearly state that duplicates is a no no for those patterned fields:
Patterned fields can have multiple occurrences as long as each has a unique name.
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#specification
The best option is to append something meaningless (like hash) to the endpoints to make them different, it could be something like:
swaggerDoc.paths.Add(endpoint + "#Access", new PathItem ...
swaggerDoc.paths.Add(endpoint + "#Refresh", new PathItem ...
How are the guys at gettyimages getting around that?
Here are some interesting findings
Their swagger json does not have those /oauth2/token paths
you can double check directly here:
https://api.gettyimages.com/swagger/docs/3
Here is how their swagger json looks like in the latest version of the swagger-ui:
http://petstore.swagger.io/?defaultModelsExpandDepth=0&docExpansion=none&url=https://api.gettyimages.com/swagger/docs/3
The version of swagger-ui they are using at gettyimages is heavily customized, I think they are injecting additional paths using JS
https://api.gettyimages.com/swagger/ui/ext/GettyImages-Resources-OAuthGrant-js
You can do that too, it will be a lot more work than just appending something to the endpoints

Real time data and retrieval plotting

So my question is as follows: I'm working on a mobile application that takes data from a vital sign sensor and sends to a telehealth server, so that a physician is able to retrieve the data from the server in real time as a plotted curve.
As I have a very weak background on this, my question is of two parts: a) how do I retrieve the data from the server in real time and b) can I use HTML5 libs or anything similar like HighCharts or Meteor charts or ZingCharts to have them plotted or is it impossible? Please be very specific as again I have a weak background on this :)
In ZingChart, you can do this in two ways:
Method 1 - Via Websockets - EX: http://www.zingchart.com/dataweek/presentation/feed.html
The websocket transport is part of the refresh/feed section, its attribute can be found here: Graph >> Refresh section of the ZingChart JSON docs. In addition, a server socket component is required and it has to follow some standard protocol in order to allow connectivity and transport with the client socket.
To get specific:
###############################
# NodeJS code
###############################
#!/usr/bin/env node
var WebSocketServer = require('websocket').server;
var http = require('http');
var server = http.createServer(function(request, response) {
response.writeHead(404);
response.end();
});
server.listen(8888, function() {
console.log((new Date()) + ' Server is listening on port 8888');
});
wsServer = new WebSocketServer({
httpServer: server,
autoAcceptConnections: false
});
function originIsAllowed(origin) {
return true;
}
wsServer.on('request', function(request) {
if (!originIsAllowed(request.origin)) {
request.reject();
console.log((new Date()) + ' Connection from origin ' + request.origin + ' rejected.');
return;
}
var type = '',
method = '',
status = 0;
var connection = request.accept('zingchart', request.origin);
connection.on('message', function(message) {
function startFeed() {
console.log('start feed');
status = 1;
if (method == 'push') {
sendFeedData();
}
}
function stopFeed() {
console.log('stop feed');
status = 0;
}
function sendFeedData() {
if (method == 'push') {
var ts = (new Date()).getTime();
var data = {
"scale-x": ts,
"plot0": [ts, parseInt(10 + 100 * Math.random(), 10)]
};
console.log('sending feed data (push)');
connection.sendUTF(JSON.stringify(data));
if (status == 1) {
iFeedTick = setTimeout(sendFeedData, 500);
}
} else if (method == 'pull') {
var data = [];
var ts = (new Date()).getTime();
for (var i = -5; i <= 0; i++) {
data.push({
"scale-x": ts + i * 500,
"plot0": [ts + i * 500, parseInt(10 + 100 * Math.random(), 10)]
});
}
console.log('sending feed data (pull)');
connection.sendUTF(JSON.stringify(data));
}
}
function sendFullData() {
var data = {
type: "bar",
series: [{
values: [
[(new Date()).getTime(), parseInt(10 + 100 * Math.random(), 10)]
]
}]
};
console.log('sending full data');
connection.sendUTF(JSON.stringify(data));
if (status == 1) {
if (method == 'push') {
setTimeout(sendFullData, 2000);
}
}
}
if (message.type === 'utf8') {
console.log('************************ ' + message.utf8Data);
switch (message.utf8Data) {
case 'zingchart.full':
type = 'full';
break;
case 'zingchart.feed':
type = 'feed';
break;
case 'zingchart.push':
method = 'push';
break;
case 'zingchart.pull':
method = 'pull';
break;
case 'zingchart.startfeed':
startFeed();
break;
case 'zingchart.stopfeed':
stopFeed();
break;
case 'zingchart.getdata':
status = 1;
if (type == 'full') {
sendFullData();
} else if (type == 'feed') {
sendFeedData();
}
break;
}
}
});
connection.on('close', function(reasonCode, description) {
status = 0;
console.log((new Date()) + ' Peer ' + connection.remoteAddress + ' disconnected.');
});
});
###############################################
# Sample JSON settings for socket transport
###############################################
refresh: {
type: "feed",
transport: "websockets",
url: "ws://198.101.197.138:8888/",
method: "push",
maxTicks: 120,
resetTimeout: 2400
}
or
refresh: {
type: "feed",
transport: "websockets",
url: "ws://198.101.197.138:8888/",
method: "pull",
interval: 3000,
maxTicks: 120,
resetTimeout: 2400
}
Method 2 - Via API - EX: http://www.zingchart.com/dataweek/presentation/api.html
In the case you described, this would involve setting intervals of time at which you would like to retrieve data from your server, and then pass that data via the API. Check out the "Feed" section in API-Methods section of the ZingChart docs.

Get child structure groups given a structure group TCM URI with Core Service

How can I get all child structure groups given a structure group TCM URI with Core Service?
I tried using this code:
ItemsFilterData sgFilter = new RepositoryItemsFilterData
{ ItemTypes = new[] { ItemType.StructureGroup },
Recursive = true,
BaseColumns = ListBaseColumns.Id };
XElement listXml;
using (CoreServiceClient client = _coreServiceProvider.GetCoreServiceClient())
{
listXml = XElement.Parse(
client.ProxyClient.GetListXml(structureGroupUri, sgFilter)
.OuterXml);
}
But I got an error saying "Unexpected item type: StructureGroup".
Starting from the Publication's URI, this works:
client.GetListXml("tcm:0-10-1", new RepositoryItemsFilterData {
ItemTypes = new[] { ItemType.StructureGroup },
Recursive = true,
BaseColumns = ListBaseColumns.Id
})
The trick is always to find the correct filter type, which in this case is RepositoryItemsFilterData.
Non-working sample
Starting from the Structure Group's URI, this returns the direct children Structure Groups. Note that that Recursive = true seems to be ignored here.
client.GetListXml("tcm:10-328-4", new OrganizationalItemItemsFilterData {
ItemTypes = new[] { ItemType.StructureGroup },
Recursive = true,
BaseColumns = ListBaseColumns.Id
})

Resources